JavaScript must be enabled to play.
Browser lacks capabilities required to play.
Upgrade or switch to another browser.
Loading…
<div class="rules-page"> <!-- ── TABS ── Les boutons sont volontairement sur UNE SEULE ligne : SugarCube convertit les retours à la ligne du passage en <br>, qui deviendraient des éléments de grille et décaleraient les onglets d'une colonne. --> <div class="rules-tabs-wrap"><button class="rules-tab rules-tab-active" id="tab-bj" onclick="rulesShowTab('bj')"><span class="rtab-icon">🃏</span><span class="rtab-label">Blackjack Rules</span></button><button class="rules-tab" id="tab-game" onclick="rulesShowTab('game')"><span class="rtab-icon">🎮</span><span class="rtab-label">How to Play — 21 Desires</span></button></div> <!-- ══════════════════════════════ TAB : BLACKJACK ══════════════════════════════ --> <div id="rules-panel-bj"> <div class="rules-header"> <h1 class="rules-title">📖 BLACKJACK <span>RULES</span></h1> <p class="rules-tagline">Everything you need to know to play like a pro</p> </div> <!-- OBJECTIF --> <div class="rules-section"> <div class="rules-section-title">🎯 Objective</div> <div class="rules-card rules-card-wide"> <div class="rules-visual"> <div class="rv-hand"> <div class="rv-card rv-card-red">A<span>♥</span></div> <div class="rv-card">K<span>♠</span></div> </div> <div class="rv-arrow">VS</div> <div class="rv-hand"> <div class="rv-card">7<span>♣</span></div> <div class="rv-card rv-card-red">8<span>♦</span></div> <div class="rv-card">6<span>♠</span></div> </div> </div> <div class="rules-text"> <p>Get closer to <strong>21</strong> than the dealer — without going over. Go over 21 and you <strong>bust</strong> and lose your bet immediately.</p> <div class="rules-chips"> <span class="rc-chip rc-good">Player 21 ✓</span> <span class="rc-chip rc-good">Dealer 21 = Push</span> <span class="rc-chip rc-bad">Over 21 = Bust 💀</span> </div> </div> </div> </div> <!-- VALEUR DES CARTES --> <div class="rules-section"> <div class="rules-section-title">🃏 Card Values</div> <div class="rules-values-grid"> <div class="rv-val-block"> <div class="rv-cards-row"> <div class="rv-card rv-sm">2<span>♠</span></div> <div class="rv-card rv-sm rv-card-red">3<span>♥</span></div> <div class="rv-card rv-sm">4<span>♣</span></div> <span class="rv-dots">···</span> <div class="rv-card rv-sm rv-card-red">9<span>♦</span></div> </div> <div class="rv-val-label">Face value</div> </div> <div class="rv-val-block"> <div class="rv-cards-row"> <div class="rv-card rv-sm">J<span>♠</span></div> <div class="rv-card rv-sm rv-card-red">Q<span>♥</span></div> <div class="rv-card rv-sm">K<span>♣</span></div> </div> <div class="rv-val-label rv-val-big">= 10</div> </div> <div class="rv-val-block rv-val-ace"> <div class="rv-cards-row"> <div class="rv-card rv-sm rv-card-ace">A<span>♠</span></div> </div> <div class="rv-val-label">= <strong>1 or 11</strong></div> <div class="rv-val-sub">Auto-adjusts to avoid bust</div> </div> </div> </div> <!-- BLACKJACK --> <div class="rules-section"> <div class="rules-section-title">⭐ Blackjack — Best Hand</div> <div class="rules-card rules-card-highlight"> <div class="rules-visual"> <div class="rv-hand rv-hand-bj"> <div class="rv-card rv-card-red rv-card-lg">A<span>♥</span></div> <div class="rv-card rv-card-lg">K<span>♠</span></div> <div class="rv-bj-badge">BJ ⭐</div> </div> </div> <div class="rules-text"> <p>An <strong>Ace + any 10-value card</strong> on your first two cards = Blackjack.</p> <p>Blackjack pays <strong>3:2</strong> — bet $100, win $150. And you earn a <strong>free pack</strong>! 🎴</p> <div class="rules-chips"> <span class="rc-chip rc-gold">Instant win</span> <span class="rc-chip rc-gold">3:2 payout</span> <span class="rc-chip rc-gold">Free pack 🎴</span> </div> </div> </div> </div> <!-- ACTIONS --> <div class="rules-section"> <div class="rules-section-title">🎮 Your Actions</div> <div class="rules-actions-grid"> <div class="rules-action-card"> <div class="rac-icon">👆</div> <div class="rac-name">HIT</div> <div class="rv-hand rv-hand-sm"> <div class="rv-card rv-sm">7<span>♠</span></div> <div class="rv-card rv-sm rv-card-red">5<span>♥</span></div> <div class="rv-card rv-sm rv-card-new">3<span>♣</span></div> </div> <p class="rac-desc">Draw another card. Keep hitting until you're happy or you bust.</p> </div> <div class="rules-action-card"> <div class="rac-icon">✋</div> <div class="rac-name">STAND</div> <div class="rv-hand rv-hand-sm"> <div class="rv-card rv-sm rv-card-red">Q<span>♦</span></div> <div class="rv-card rv-sm">8<span>♣</span></div> </div> <p class="rac-desc">Keep your current hand. The dealer then plays their turn.</p> </div> <div class="rules-action-card rules-action-card-gold"> <div class="rac-icon">✖️2</div> <div class="rac-name">DOUBLE DOWN</div> <div class="rv-hand rv-hand-sm"> <div class="rv-card rv-sm rv-card-red">A<span>♥</span></div> <div class="rv-card rv-sm">6<span>♠</span></div> <div class="rv-card rv-sm rv-card-facedown">?</div> </div> <p class="rac-desc">Double your bet and receive exactly <strong>one</strong> more card. High risk, high reward.</p> <div class="rac-condition">Only on first two cards</div> </div> <div class="rules-action-card rules-action-card-purple"> <div class="rac-icon">✂️</div> <div class="rac-name">SPLIT</div> <div class="rv-split-visual"> <div class="rv-hand rv-hand-sm"> <div class="rv-card rv-sm">8<span>♠</span></div> <div class="rv-card rv-sm rv-card-new">4<span>♣</span></div> </div> <div class="rv-split-arrow">⟵ ⟶</div> <div class="rv-hand rv-hand-sm"> <div class="rv-card rv-sm rv-card-red">8<span>♥</span></div> <div class="rv-card rv-sm rv-card-new">K<span>♦</span></div> </div> </div> <p class="rac-desc">If both cards are the same value, split into <strong>two separate hands</strong>. Each needs its own bet.</p> <div class="rac-condition">Only on matching pairs</div> </div> </div> </div> <!-- DEALER --> <div class="rules-section"> <div class="rules-section-title">🤖 The Dealer</div> <div class="rules-card"> <div class="rules-visual rules-visual-col"> <div class="rv-rule-row"> <div class="rv-card rv-sm">7<span>♣</span></div> <div class="rv-card rv-sm rv-card-facedown">?</div> <span class="rv-rule-label">Dealer shows one card face-down</span> </div> <div class="rv-rule-row"> <div class="rv-rule-icon">📏</div> <span class="rv-rule-label">Dealer <strong>must hit</strong> on 16 or below</span> </div> <div class="rv-rule-row"> <div class="rv-rule-icon">🛑</div> <span class="rv-rule-label">Dealer <strong>must stand</strong> on 17 or above</span> </div> </div> </div> </div> <!-- OUTCOMES --> <div class="rules-section"> <div class="rules-section-title">💰 Payouts</div> <div class="rules-payouts-grid"> <div class="rp-row rp-win"> <span class="rp-icon">✅</span> <span class="rp-label">Win</span> <span class="rp-value">1 : 1 · Bet $100 → +$100</span> </div> <div class="rp-row rp-bj"> <span class="rp-icon">⭐</span> <span class="rp-label">Blackjack</span> <span class="rp-value">3 : 2 · Bet $100 → +$150</span> </div> <div class="rp-row rp-push"> <span class="rp-icon">🤝</span> <span class="rp-label">Push (Tie)</span> <span class="rp-value">0 : 0 · Bet returned</span> </div> <div class="rp-row rp-lose"> <span class="rp-icon">❌</span> <span class="rp-label">Lose / Bust</span> <span class="rp-value">-1 : 1 · Bet $100 → -$100</span> </div> </div> </div> <!-- TOKENS --> <div class="rules-section"> <div class="rules-section-title">🎫 Token System</div> <div class="rules-card rules-card-token"> <div class="rules-text"> <p>Every time you <strong>win a hand</strong>, you earn tokens for the table you played at.</p> <div class="rules-token-formula"> <span class="rtf-label">Winnings</span> <span class="rtf-op">÷ 10</span> <span class="rtf-eq">=</span> <span class="rtf-result">🎫 Tokens</span> </div> <p class="rtf-example">Win $200 → earn <strong>20 tokens</strong> for that table</p> <p>Tokens are spent in the <strong>Card Shop</strong> to open packs. They're separate per table and never reset.</p> </div> </div> </div> </div><!-- end #rules-panel-bj --> <!-- ══════════════════════════════ TAB : GAME RULES ══════════════════════════════ --> <div id="rules-panel-game" style="display:none;"> <div class="rules-header"> <h1 class="rules-title">🎮 THE <span>21 DESIRES</span></h1> <p class="rules-tagline">The complete game guide</p> </div> <!-- SEPARATION DES MONDES --> <div class="rules-section"> <div class="rules-section-title">🌍 Three Separate Worlds</div> <div class="rules-card rules-card-wide"> <div class="rules-text"> <p>Straight, Gay and Trans are <strong>completely independent</strong>. Switching world is not just a change of scenery — you step into a separate account.</p> <div class="rules-payouts-grid" style="margin-top:6px;"> <div class="rp-row rp-push"><span class="rp-icon">💰</span><span class="rp-label">Balance</span><span class="rp-value">One bankroll per world</span></div> <div class="rp-row rp-push"><span class="rp-icon">🎫</span><span class="rp-label">Tokens</span><span class="rp-value">Per table, never shared</span></div> <div class="rp-row rp-push"><span class="rp-icon">🎴</span><span class="rp-label">Collection</span><span class="rp-value">Each world has its own cards</span></div> <div class="rp-row rp-push"><span class="rp-icon">🎡</span><span class="rp-label">Lucky Wheel</span><span class="rp-value">Counts hands per world</span></div> <div class="rp-row rp-lose"><span class="rp-icon">💀</span><span class="rp-label">Game Over</span><span class="rp-value">Only hits the world you played</span></div> </div> <p style="margin-top:10px;">Winning big in one world will never fund another, and losing everything in one leaves the others exactly as you left them.</p> </div> </div> </div> <!-- 01 BLACKJACK --> <div class="rules-section"> <div class="rules-section-title">01 — Play Blackjack</div> <div class="rules-card rules-card-wide"> <div class="rules-text"> <p>Pick a table in the Casino, set your bet and beat the dealer. Hit, Stand, Double Down or Split — standard Blackjack rules apply. Stay under 22. Dealer hits below 17.</p> <div class="rules-chips"> <span class="rc-chip rc-good">$50 · $100 · $250 bets</span> <span class="rc-chip rc-gold">Blackjack = 3:2 + free pack 🎴</span> <span class="rc-chip rc-good">Start with $1,500</span> <span class="rc-chip rc-bad">Below $50 = Game Over 💀</span> </div> </div> </div> </div> <!-- 02 TOKENS --> <div class="rules-section"> <div class="rules-section-title">02 — Earn Tokens</div> <div class="rules-card rules-card-token"> <div class="rules-text"> <p>Win a hand → earn tokens for that table. Tokens are <strong>per-table</strong> and never reset unless you hit Game Over.</p> <div class="rules-token-formula"> <span class="rtf-label">Winnings</span> <span class="rtf-op">÷ 10</span> <span class="rtf-eq">=</span> <span class="rtf-result">🎫 Tokens</span> </div> <p class="rtf-example">$50 bet → +10 🎫 · $100 → +20 🎫 · $250 → +50 🎫</p> </div> </div> </div> <!-- 03 WIN STREAK --> <div class="rules-section"> <div class="rules-section-title">03 — Win Streak</div> <div class="rules-card rules-card-wide"> <div class="rules-text"> <p>Win hands <strong>back-to-back</strong> on the same table and your streak climbs — hit milestones for bonus rewards on top of your normal winnings. A <strong>single loss resets it to zero</strong>. Streaks are tracked <strong>per table</strong>.</p> <div class="rules-payouts-grid" style="margin-top:8px;"> <div class="rp-row rp-win"><span class="rp-icon">🔥</span><span class="rp-label">×3 streak</span><span class="rp-value">+20 tokens</span></div> <div class="rp-row rp-win"><span class="rp-icon">🔥</span><span class="rp-label">×5 streak</span><span class="rp-value">+$150 cash</span></div> <div class="rp-row rp-gold"><span class="rp-icon">🔥</span><span class="rp-label">×6 streak</span><span class="rp-value">1 free pack</span></div> <div class="rp-row rp-gold"><span class="rp-icon">👑</span><span class="rp-label">×8 streak</span><span class="rp-value">New-cards-only pack + $300</span></div> </div> <p style="margin-top:8px;">The ×8 pack is special: every card inside is guaranteed to be one you don't already own.</p> </div> </div> </div> <!-- 04 TABLES --> <div class="rules-section"> <div class="rules-section-title">04 — Unlock Tables</div> <div class="rules-card rules-card-wide"> <div class="rules-text"> <p>Tables unlock as your <strong>total earnings</strong> grow. Each table has its own card packs with increasingly explicit content.</p> <div class="rules-payouts-grid" style="margin-top:8px;"> <div class="rp-row rp-win"><span class="rp-icon">🃏</span><span class="rp-label">The Vice</span><span class="rp-value">Available at start</span></div> <div class="rp-row rp-win"><span class="rp-icon">💋</span><span class="rp-label">The Boudoir</span><span class="rp-value">Earn $5,000</span></div> <div class="rp-row rp-bj"><span class="rp-icon">🔥</span><span class="rp-label">The Heat</span><span class="rp-value">Earn $20,000</span></div> <div class="rp-row rp-bj"><span class="rp-icon">💀</span><span class="rp-label">The Slut Pit</span><span class="rp-value">Earn $30,000</span></div> <div class="rp-row rp-lose"><span class="rp-icon">👑</span><span class="rp-label">Hetero VIP</span><span class="rp-value">Earn $75,000 ✦ Coming Soon</span></div> </div> </div> </div> </div> <!-- 05 PACKS --> <div class="rules-section"> <div class="rules-section-title">05 — Open Packs & Collect</div> <div class="rules-card rules-card-highlight"> <div class="rules-text"> <p>Spend tokens in the <strong>Card Shop</strong> to open packs. Each pack reveals <strong>3 animated adult cards</strong> one by one. Cards are drawn randomly by rarity weight.</p> <div class="rules-chips" style="margin-top:6px;"> <span class="rc-chip" style="background:rgba(160,160,176,0.12);color:#a0a0b0;border-color:rgba(160,160,176,0.25);">⚪ Common — 60%</span> <span class="rc-chip" style="background:rgba(79,195,247,0.1);color:#4fc3f7;border-color:rgba(79,195,247,0.28);">🔵 Rare — 25%</span> <span class="rc-chip" style="background:rgba(191,0,255,0.1);color:#c084fc;border-color:rgba(191,0,255,0.28);">🟣 Epic — 12%</span> <span class="rc-chip rc-gold">🌟 Legendary — 3%</span> </div> <p style="margin-top:8px;">Duplicate cards stack in your collection. Sell them for cash or transmute them into cards you're missing (see <strong>08 — Duplicates</strong>).</p> </div> </div> </div> <!-- 06 LUCKY WHEEL --> <div class="rules-section"> <div class="rules-section-title">06 — Lucky Wheel</div> <div class="rules-card"> <div class="rules-text"> <p>Every <strong>20 hands played</strong>, the Lucky Wheel lights up in the sidebar. Spin it for free — you always win something.</p> <div class="rules-payouts-grid" style="margin-top:6px;"> <div class="rp-row rp-win"><span class="rp-icon">💰</span><span class="rp-label">Cash</span><span class="rp-value">$100 / $150 / $200 / $300 / $500★</span></div> <div class="rp-row rp-win"><span class="rp-icon">🎫</span><span class="rp-label">Tokens</span><span class="rp-value">50 / 75 / 100 — choose your table</span></div> <div class="rp-row rp-gold"><span class="rp-icon">🎴</span><span class="rp-label">Free Pack</span><span class="rp-value">Choose your table</span></div> </div> </div> </div> </div> <!-- 07 GAME OVER --> <div class="rules-section"> <div class="rules-section-title">07 — Game Over</div> <div class="rules-card"> <div class="rules-text"> <p>Balance drops below <strong>$50</strong> → Game Over. Your balance resets to <strong>$1,500</strong>, but you lose a portion of your collection and tokens. <strong>Penalties escalate</strong> with each game over.</p> <p><strong>Each world is self-contained.</strong> A Game Over only affects the world you were playing in — your balance, cards and tokens in the other worlds are untouched, and every world counts its own escalating penalties.</p> <div class="rules-payouts-grid" style="margin-top:6px;"> <div class="rp-row rp-push"><span class="rp-icon">1️⃣</span><span class="rp-label">1st Game Over</span><span class="rp-value">Lose 1/4 of collection & tokens</span></div> <div class="rp-row rp-push"><span class="rp-icon">2️⃣</span><span class="rp-label">2nd</span><span class="rp-value">Lose 1/3</span></div> <div class="rp-row rp-bj"><span class="rp-icon">3️⃣</span><span class="rp-label">3rd</span><span class="rp-value">Lose 1/2</span></div> <div class="rp-row rp-bj"><span class="rp-icon">4️⃣</span><span class="rp-label">4th</span><span class="rp-value">Lose 3/4</span></div> <div class="rp-row rp-lose"><span class="rp-icon">💀</span><span class="rp-label">5th+</span><span class="rp-value">Lose EVERYTHING</span></div> </div> </div> </div> </div> <!-- 08 DOUBLONS : SELL CARDS + TRANSMUTE --> <div class="rules-section"> <div class="rules-section-title">08 — Duplicates: Sell or Transmute</div> <div class="rules-card rules-card-token"> <div class="rules-text"> <p>Got multiple copies of the same card? You always keep 1 copy of every card, and you have two ways to use the extras. Both are in the <strong>Duplicates</strong> block of the sidebar.</p> <p style="margin-top:10px;"><strong>💱 Sell Cards</strong> — turn extras into cash, paid straight to your balance.</p> <div class="rules-chips"> <span class="rc-chip" style="background:rgba(160,160,176,0.1);color:#a0a0b0;border-color:rgba(160,160,176,0.2);">⚪ Common — $10</span> <span class="rc-chip" style="background:rgba(79,195,247,0.08);color:#4fc3f7;border-color:rgba(79,195,247,0.22);">🔵 Rare — $30</span> <span class="rc-chip" style="background:rgba(191,0,255,0.08);color:#c084fc;border-color:rgba(191,0,255,0.22);">🟣 Epic — $80</span> <span class="rc-chip rc-gold">🌟 Legendary — $200</span> </div> <p style="margin-top:12px;"><strong>💠 Transmute</strong> — break extras down into Essence, then spend it on a card you don't own yet. You pick the rarity, the card is drawn among the ones you're missing.</p> <div class="rules-payouts-grid" style="margin-top:6px;"> <div class="rp-row rp-essence"><span class="rp-icon">♻️</span><span class="rp-label">Essence per spare</span><span class="rp-value">⚪ 5 · 🔵 15 · 🟣 40 · 🌟 100</span></div> <div class="rp-row rp-essence"><span class="rp-icon">💠</span><span class="rp-label">Missing card cost</span><span class="rp-value">⚪ 40 · 🔵 80 · 🟣 200 · 🌟 600</span></div> </div> <p style="margin-top:8px;">Selling pays now, transmuting fills your collection. Each world keeps its own Essence.</p> </div> </div> </div> </div><!-- end #rules-panel-game --> </div> <<done>> <<script>> (function() { var navLinks = document.querySelectorAll('.casino-nav a'); navLinks.forEach(function(l) { l.classList.add('btn','btn-stand'); }); /* Tab switch function — global scope so onclick works */ window.rulesShowTab = function(tab) { document.getElementById('rules-panel-bj').style.display = tab === 'bj' ? '' : 'none'; document.getElementById('rules-panel-game').style.display = tab === 'game' ? '' : 'none'; document.getElementById('tab-bj').classList.toggle('rules-tab-active', tab === 'bj'); document.getElementById('tab-game').classList.toggle('rules-tab-active', tab === 'game'); }; /* Check if we arrived via a "game rules" deep-link */ if (window._rulesOpenTab === 'game') { window.rulesShowTab('game'); delete window._rulesOpenTab; } })(); <</script>> <</done>> <style> /* ── Rules Tabs ── */ .rules-tabs-wrap { display: grid; grid-template-columns: 1fr 1fr; gap: 0; width: 100%; box-sizing: border-box; margin-bottom: 12px; border-radius: 14px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); } /* Garde-fou : un <br> injecté par SugarCube deviendrait un élément de grille et décalerait les onglets d'une colonne (bug d'affichage constaté). */ .rules-tabs-wrap br { display: none; } .rules-tab { font-family: 'Orbitron', monospace; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.1em; padding: 13px 18px; border: none; background: rgba(255,255,255,0.03); color: rgba(210,200,235,0.4); cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 9px; min-width: 0; border-right: 1px solid rgba(255,255,255,0.08); } .rules-tab:last-child { border-right: none; } .rtab-icon { font-size: 1.15rem; } .rtab-label { line-height: 1.25; } .rules-tab:hover { background: rgba(255,255,255,0.07); color: rgba(210,200,235,0.75); } .rules-tab-active { background: rgba(255,45,120,0.14) !important; color: var(--neon-pink) !important; box-shadow: inset 0 -3px 0 var(--neon-pink); } </style>
<<silently>><<set $gameState = "betting">><</silently>> <div id="table-banner"></div> <div id="streak-bar-wrap"></div> <<if $playerMoney >= 50>> <div class="betting-screen"> <div class="betting-hero"> <div class="betting-hero-label">◈ Choose Your Stakes ◈</div> <h2 class="betting-hero-title">Place Your Bet</h2> <p class="betting-hero-sub">Win a hand to earn tokens — the bigger the bet, the more tokens you pocket!</p> </div> <div class="betting-grid"> <<if $playerMoney >= 50>><<link "Bet $50" "Deal Cards">><<set $currentBet = 50>><<set $baseBet = 50>><</link>><</if>> <<if $playerMoney >= 100>><<link "Bet $100" "Deal Cards">><<set $currentBet = 100>><<set $baseBet = 100>><</link>><</if>> <<if $playerMoney >= 250>><<link "Bet $250" "Deal Cards">><<set $currentBet = 250>><<set $baseBet = 250>><</link>><</if>> </div> </div> <<done>> <<script>> (function() { /* ── Bannière table dynamique ── */ var v = State.variables; var banner = document.getElementById('table-banner'); if (banner) { var tableObj = window.TABLES ? window.TABLES.find(function(t) { return t.id === v.activeTable; }) : null; var tableName = tableObj ? tableObj.name : v.activeTable; var tableColor = tableObj ? tableObj.color : 'linear-gradient(90deg,#ff2d78,#bf00ff)'; var tokens = v.tokens ? (v.tokens[v.activeTable] || 0) : 0; var branchLabels = { hetero:'Straight', gay:'Gay', trans:'Trans' }; var branchLabel = branchLabels[v.activeBranch] || ''; var TABLE_LOGO = { 'vice': 'Images/icons/logo_vice.png', 'boudoir': 'Images/icons/logo_boudoir.png', 'heat': 'Images/icons/logo_heat.png', 'slutpit': 'Images/icons/logo_slutpit.png', 'heterovip': 'Images/icons/logo_heterovip.png', 'rainbow': 'Images/icons/logo_rainbow.png', 'gayvip': 'Images/icons/logo_gayvip.png', 'transroom': 'Images/icons/logo_transroom.png', 'transvip': 'Images/icons/logo_transvip.png', 'penthouse': 'Images/icons/logo_penthouse.png' }; var logoSrc = TABLE_LOGO[v.activeTable] || ''; var logoBlock = logoSrc ? '<div class="table-banner-logo-frame">' + '<img class="table-banner-logo" src="' + logoSrc + '" alt="" ' + 'onerror="this.closest(\'.table-banner-logo-frame\').remove(); document.getElementById(\'table-banner\').classList.add(\'table-banner-no-logo\');">' + '</div>' : ''; if (!logoSrc) banner.classList.add('table-banner-no-logo'); banner.innerHTML = '<div class="table-banner-row">' + logoBlock + '<div class="table-banner-pill">' + '<div class="table-banner-bar" style="background:' + tableColor + '"></div>' + '<div class="table-banner-body">' + '<div class="table-banner-left">' + '<div class="table-banner-branch">' + branchLabel + '</div>' + '<div class="table-banner-name">' + tableName + '</div>' + '</div>' + '<div class="table-banner-right">' + '<div class="table-banner-stat">' + '<span class="tbs-label">💰 Balance</span>' + '<span class="tbs-value">' + formatMoney(v.playerMoney) + '</span>' + '</div>' + '<div class="table-banner-stat">' + '<span class="tbs-label">🎫 Tokens</span>' + '<span class="tbs-value tbs-tokens">' + tokens + '</span>' + '</div>' + '<a class="table-banner-back" id="tbn-back-casino">← Casino</a>' + '</div>' + '</div>' + '</div>' + '</div>'; var backBtn = document.getElementById('tbn-back-casino'); if (backBtn) backBtn.addEventListener('click', function() { Engine.play('Casino'); }); } /* Bet amount → tokens si victoire normale (mise x2, /10) */ const betData = [ { amount: 50, tokens: 10, label: '💵 Standard', desc: 'Safe play', cls: '' }, { amount: 100, tokens: 20, label: '🔥 High Roll', desc: 'Go bold', cls: 'bet-btn-hot' }, { amount: 250, tokens: 50, label: '💎 All In', desc: 'Fortune favors', cls: 'bet-btn-vip' } ]; const grid = document.querySelector('.betting-grid'); if (!grid) return; const eligible = betData.filter(b => b.amount <= v.playerMoney); const links = grid.querySelectorAll('a'); links.forEach((link, i) => { const bet = eligible[i]; if (!bet) return; link.classList.add('bet-btn'); if (bet.cls) link.classList.add(bet.cls); link.innerHTML = '<span class="bet-label">' + bet.label + '</span>' + '<span class="bet-amount">$' + bet.amount + '</span>' + '<span class="bet-desc">' + bet.desc + '</span>' + '<span class="bet-tokens">🎫 +' + bet.tokens + ' tokens if win</span>'; }); var _TBG = { 'vice': 'Images/icons/icon_thevice.png', 'boudoir': 'Images/icons/icon_boudoir.png', 'heat': 'Images/icons/icon_heat.png', 'slutpit': 'Images/icons/icon_slutpit.png', 'heterovip': 'Images/icons/icon_heterovip.png', 'rainbow': 'Images/scenes/the_rainbow.png', 'gayvip': 'Images/scenes/the_gay_vip.png', 'transroom': 'Images/icons/icon_transroom.png', 'velvet': 'Images/icons/icon_velvet.png', 'dollhouse': 'Images/icons/icon_dollhouse.png', 'herturn': 'Images/icons/icon_herturn.png', 'trance': 'Images/icons/icon_trance.png', 'transvip': 'Images/icons/icon_transvip.png', 'penthouse': 'Images/scenes/the_penthouse.png' }; (function applyTableBg() { var at = State.variables.activeTable; var img = at && _TBG[at] ? _TBG[at] : null; document.body.style.backgroundImage = img ? 'linear-gradient(rgba(0,0,0,0.62),rgba(0,0,0,0.55),rgba(0,0,0,0.62)), url("' + img + '")' : ''; document.body.style.backgroundSize = 'cover'; document.body.style.backgroundPosition = 'center'; document.body.style.backgroundAttachment = 'fixed'; })(); window.showFirstVisitTip('blackjack_table', { icon: '🃏', title: 'Place Your Bet', body: 'Choose how much you want to wager before the cards are dealt. The higher the bet, the more tokens you earn on a win.', bodyScarlett: 'Before we start — think carefully about your bet. The bigger the risk, the more tokens you walk away with. I like players who aren\'t afraid.', bodyTrans: 'Place your bet, sweetie. The bigger the bet, the more tokens you pocket when you win. Don\'t be shy, nobody in here plays it safe.', tips: [ '💰 $50 → +10 tokens on win', '💰 $100 → +20 tokens on win', '💰 $250 → +50 tokens on win', '⭐ Blackjack pays 3:2 and grants a free pack' ] }); /* ── STREAK BAR ── */ (function() { var wrap = document.getElementById('streak-bar-wrap'); if (!wrap) return; var streak = (v.currentStreak && v.currentStreak[v.activeTable]) ? v.currentStreak[v.activeTable] : 0; var milestones = [3, 5, 6, 8]; var icons = ['🔥','🔥','🔥','👑']; var labels = ['+20 tkn', '+$150', 'Free Pack', 'New Pack +$300']; var segments = milestones.map(function(m, i) { var reached = streak >= m; var active = streak === m; return '<div class="streak-seg' + (reached ? ' streak-seg-done' : '') + (active ? ' streak-seg-active' : '') + '">' + '<div class="streak-seg-icon">' + icons[i] + '</div>' + '<div class="streak-seg-num">×' + m + '</div>' + '<div class="streak-seg-label">' + labels[i] + '</div>' + '</div>'; }).join('<div class="streak-line' + (streak > 0 ? ' streak-line-active' : '') + '"></div>'); wrap.innerHTML = '<div id="streak-bar">' + '<div class="streak-title">' + (streak > 0 ? '🔥 <span class="streak-count">' + streak + '</span> win streak' : '🔥 Win Streak') + '</div>' + '<div class="streak-milestones">' + segments + '</div>' + '</div>'; })(); })(); <</script>> <</done>> <<else>> <<goto "Game Over">> <</if>>
<div class="casino-main shop-main"> <div id="shop-brand-bar"></div> <div class="shop-rarity-legend"> <span class="rchip rchip-c">⚪ Common 60%</span> <span class="rchip rchip-r">🔵 Rare 25%</span> <span class="rchip rchip-e">🟣 Epic 12%</span> <span class="rchip rchip-l">🌟 Legendary 3%</span> </div> <div id="shop-container"></div> </div> <<done>> <<script>> (function() { var v = State.variables; var container = document.getElementById('shop-container'); if (!container) return; /* Emblèmes de table — mêmes visuels que la bannière du Blackjack Table. Le Casino utilise les photos d'ambiance (icon_*), le Shop les logos (logo_*) : c'est le marqueur visuel qui distingue les deux écrans. */ var TABLE_LOGO = { 'vice': 'Images/icons/logo_vice.png', 'boudoir': 'Images/icons/logo_boudoir.png', 'heat': 'Images/icons/logo_heat.png', 'slutpit': 'Images/icons/logo_slutpit.png', 'heterovip': 'Images/icons/logo_heterovip.png', 'rainbow': 'Images/icons/logo_rainbow.png', 'gayvip': 'Images/icons/logo_gayvip.png', 'transroom': 'Images/icons/logo_transroom.png', 'transvip': 'Images/icons/logo_transvip.png', 'penthouse': 'Images/icons/logo_penthouse.png' }; /* Titre dynamique selon branche */ /* Le bandeau est construit plus bas, une fois la progression calculee. */ if (!v.tokens) v.tokens = { vice:0, boudoir:0, heat:0, slutpit:0, heterovip:0, rainbow:0, gayvip:0, transroom:0, velvet:0, dollhouse:0, herturn:0, trance:0, transvip:0, penthouse:0 }; /* ── Détention par catégorie ── */ function catStat(tag) { var col = v.collection || {}; var cards = CARD_DB.filter(function(c) { return c.category === tag; }); return { label: window.categoryLabel(tag), owned: cards.filter(function(c) { return (col[c.id] || 0) > 0; }).length, total: cards.length }; } function packStats(pack) { var col = v.collection || {}; var cards = CARD_DB.filter(function(c) { return pack.categories.indexOf(c.category) !== -1; }); return { owned: cards.filter(function(c) { return (col[c.id] || 0) > 0; }).length, total: cards.length }; } /* Tags de catégorie avec compteur de détention */ function catTagsHTML(pack, dim) { return pack.categories.map(function(tag) { var s = catStat(tag); var cls = 'shop-cat' + (dim ? ' shop-cat-dim' : '') + (s.total === 0 ? ' shop-cat-void' : (s.owned === s.total ? ' shop-cat-full' : '')); return '<span class="' + cls + '">' + '<span class="shop-cat-name">' + s.label + '</span>' + '<span class="shop-cat-num">' + s.owned + '/' + s.total + '</span>' + '</span>'; }).join(''); } /* Cadre emblème réutilisé par toutes les sections */ function logoHTML(table, cls) { var src = TABLE_LOGO[table.id]; if (!src) return '<div class="shop-emblem ' + (cls || '') + '"><span class="shop-emblem-fallback">' + table.emoji + '</span></div>'; return '<div class="shop-emblem ' + (cls || '') + '">' + '<img src="' + src + '" alt="" onerror="this.parentNode.innerHTML=\'<span class="shop-emblem-fallback">' + table.emoji + '</span>\'">' + '</div>'; } var branchFilter = v.activeBranch; var branchEarned = v.activeBranch === 'gay' ? v.earnedGay : v.activeBranch === 'trans' ? v.earnedTrans : v.earnedHetero; TABLES.forEach(function(table) { if (table.branch !== branchFilter && table.branch !== 'both') return; /* Hetero VIP et Penthouse : contenu pas encore prêt, verrouillées et affichées en Coming Soon quel que soit l'état de déblocage réel (Penthouse contourne donc aussi son propre pré-requis normal pour rester visible ici). Slut Pit a du contenu partiel (plusieurs catégories alimentées) et est donc débloquable normalement. Même liste que dans le Casino — à ajuster ensemble. */ var FORCED_COMING_SOON = ['heterovip', 'penthouse', 'transvip', 'velvet', 'dollhouse', 'herturn', 'trance']; if (table.branch === 'both' && FORCED_COMING_SOON.indexOf(table.id) === -1) { if (!v.unlockedTables.includes('heterovip') || !v.unlockedTablesGay.includes('gayvip')) return; } /* Penthouse volontairement absent de la Boutique pour le moment : retirer cette ligne le jour ou la table sera ouverte. */ if (table.id === 'penthouse') return; var tablePacks = PACKS.filter(function(p) { return p.table === table.id; }); if (!tablePacks.length) return; var isUnlocked = ( table.branch === 'hetero' ? v.unlockedTables.indexOf(table.id) !== -1 : table.branch === 'gay' ? v.unlockedTablesGay.indexOf(table.id) !== -1 : table.branch === 'trans' ? v.unlockedTablesTrans.indexOf(table.id) !== -1 : v.unlockedTables.indexOf('penthouse') !== -1 ); if (FORCED_COMING_SOON.indexOf(table.id) !== -1) isUnlocked = false; /* "Coming soon" veut dire : la branche entière n'a encore aucune carte disponible (gay/trans), pas simplement "la table n'est pas encore débloquée" — sinon Heat, Slut Pit etc. resteraient bloqués en permanence même une fois réellement débloqués. */ var isComingSoon = !isUnlocked && (table.branch === 'gay' || FORCED_COMING_SOON.indexOf(table.id) !== -1); /* Total de la table, pour la barre d'en-tête et le cumul global */ var mainPack = tablePacks.find(function(p) { return !p.forcedRarity; }) || tablePacks[0]; var tStats = packStats(mainPack); var tPct = tStats.total > 0 ? Math.round(tStats.owned / tStats.total * 100) : 0; var section = document.createElement('div'); var tokens = v.tokens[table.id] || 0; section.dataset.table = table.id; /* ── TABLE SCELLEE : ni nom, ni emblème, ni catégories ── Même liste que dans le Casino : si le nom est caché là-bas, il doit l'être ici aussi, sinon le secret fuite. */ if (!isUnlocked && window.mysteryTables().indexOf(table.id) !== -1) { var SHOP_TEASERS = { velvet: 'Softer than it looks. Not yet.', dollhouse: 'The house is not ready to show this one.', herturn: 'Patience. She decides when.', trance: 'Look away. You are not ready.', transvip: 'Behind the velvet rope. Earn your way in.' }; section.className = 'shop-block shop-block-mystery'; section.innerHTML = '<div class="shop-block-head">' + '<div class="shop-myst-emblem">?</div>' + '<div class="shop-block-id">' + '<div class="shop-myst-name">— — — — —</div>' + '<div class="shop-myst-teaser">' + (SHOP_TEASERS[table.id] || 'This table keeps its secrets.') + '</div>' + '</div>' + '<span class="shop-myst-flag">Coming Soon</span>' + '</div>'; container.appendChild(section); return; } /* ── COMING SOON ── */ if (isComingSoon) { section.className = 'shop-block shop-block-cs'; section.innerHTML = '<div class="shop-block-head">' + logoHTML(table, 'shop-emblem-dim') + '<div class="shop-block-id">' + '<div class="shop-block-name">' + table.name + '</div>' + '<div class="shop-block-meta">' + tablePacks.length + ' pack' + (tablePacks.length > 1 ? 's' : '') + ' in preparation</div>' + '</div>' + '<span class="shop-flag shop-flag-cs">✦ Coming Soon</span>' + '</div>' + '<div class="shop-block-body">' + '<div class="shop-cs-note">Content for this table is still being produced.</div>' + '<div class="shop-cats">' + catTagsHTML(mainPack, true) + '</div>' + '</div>'; container.appendChild(section); return; } /* ── EN-TÊTE DE TABLE ── */ section.className = 'shop-block' + (isUnlocked ? '' : ' shop-block-locked'); var headRight = isUnlocked ? '<div class="shop-tokens"><span class="shop-tokens-num">' + tokens + '</span><span class="shop-tokens-lbl">🎫 tokens</span></div>' : '<span class="shop-flag shop-flag-locked">🔒 Locked</span>'; var headHTML = '<div class="shop-block-head">' + '<div class="shop-block-accent" style="background:' + table.color + (isUnlocked ? '' : ';opacity:0.3') + '"></div>' + logoHTML(table, isUnlocked ? '' : 'shop-emblem-dim') + '<div class="shop-block-id">' + '<div class="shop-block-name">' + table.name + '</div>' + '<div class="shop-block-meta">' + tStats.owned + ' / ' + tStats.total + ' cards collected · ' + tPct + '%</div>' + '<div class="shop-head-bar"><div class="shop-head-fill" style="width:' + tPct + '%"></div></div>' + '</div>' + headRight + '</div>'; section.innerHTML = headHTML; /* ── TABLE VERROUILLÉE : progression de déblocage ── */ if (!isUnlocked) { var upct = table.threshold > 0 ? Math.min(100, Math.round(branchEarned / table.threshold * 100)) : 100; var body = document.createElement('div'); body.className = 'shop-block-body'; body.innerHTML = '<div class="shop-cats">' + catTagsHTML(mainPack, true) + '</div>' + '<div class="shop-unlock">' + '<div class="shop-unlock-head">' + '<span class="shop-unlock-earned">' + window.formatMoney(branchEarned) + '</span>' + '<span class="shop-unlock-goal">' + window.formatMoney(table.threshold) + '</span>' + '</div>' + '<div class="shop-unlock-bar"><div class="shop-unlock-fill" style="width:' + upct + '%"></div></div>' + '<div class="shop-unlock-note">🔒 Unlocks at <strong>' + window.formatMoney(table.threshold) + '</strong> earned</div>' + '</div>'; section.appendChild(body); container.appendChild(section); return; } /* ── PACKS DE LA TABLE ── */ var body = document.createElement('div'); body.className = 'shop-block-body'; tablePacks.forEach(function(pack) { var ps = packStats(pack); var pPct = ps.total > 0 ? Math.round(ps.owned / ps.total * 100) : 0; var isLegend = !!pack.forcedRarity; var xxlPrice = window.packPrice(pack, true); var row = document.createElement('div'); row.className = 'shop-pack' + (isLegend ? ' shop-pack-legend' : ''); /* Deux formats d'achat : classique (3 cartes) et XXL (9 cartes, 3× le prix) */ function buyBtn(xxl) { var price = xxl ? xxlPrice : pack.price; var count = xxl ? window.PACK_XXL_CARDS : 3; var cls = 'shop-buy2' + (xxl ? ' shop-buy2-xxl' : '') + (isLegend ? ' shop-buy2-legend' : ''); if (ps.total === 0) { return '<button class="' + cls + '" disabled>' + '<span class="sb2-top">×' + count + '</span>' + '<span class="sb2-price">No cards</span>' + '</button>'; } if (tokens < price) { return '<button class="' + cls + '" disabled title="You need ' + price + ' tokens on this table">' + '<span class="sb2-top">' + (xxl ? 'XXL ×' + count : '×' + count) + '</span>' + '<span class="sb2-price sb2-price-short">' + price + ' 🎫</span>' + '</button>'; } return '<button class="' + cls + ' is-on" data-pack="' + pack.id + '" data-xxl="' + (xxl ? '1' : '0') + '">' + '<span class="sb2-top">' + (xxl ? 'XXL ×' + count : '×' + count) + '</span>' + '<span class="sb2-price">' + price + ' 🎫</span>' + '</button>'; } row.innerHTML = '<div class="shop-pack-main">' + '<div class="shop-pack-info">' + '<div class="shop-pack-top">' + '<span class="shop-pack-emoji">' + pack.emoji + '</span>' + '<span class="shop-pack-name">' + pack.name + '</span>' + (isLegend ? '<span class="shop-flag shop-flag-legend">Guaranteed</span>' : '') + '</div>' + (isLegend ? '' : '<div class="shop-cats">' + catTagsHTML(pack, false) + '</div>') + '<div class="shop-pack-prog">' + '<div class="shop-pack-bar"><div class="shop-pack-fill" style="width:' + pPct + '%"></div></div>' + '<span class="shop-pack-count">' + ps.owned + ' / ' + ps.total + '</span>' + '</div>' + '</div>' + '<div class="shop-pack-buy">' + buyBtn(false) + buyBtn(true) + '</div>' + '</div>'; body.appendChild(row); }); section.appendChild(body); container.appendChild(section); }); /* ── Progression de la collection, limitee a la branche active ── */ var shopBrand = document.getElementById('shop-brand-bar'); if (shopBrand && window.branchBanner) { var col = v.collection || {}; var bTags = (window.CATEGORIES || []) .filter(function(c) { return c.branch === v.activeBranch; }) .map(function(c) { return c.tag; }); var bCards = CARD_DB.filter(function(c) { return bTags.indexOf(c.category) !== -1; }); var gTotal = bCards.length; var gOwned = bCards.filter(function(c) { return (col[c.id] || 0) > 0; }).length; var gPct = gTotal > 0 ? Math.round(gOwned / gTotal * 100) : 0; shopBrand.innerHTML = window.branchBanner('Card Shop', '<div class="brand-aside-label">Collection</div>' + '<div class="brand-aside-value">' + gOwned + '<em> / ' + gTotal + '</em></div>' + '<div class="brand-aside-bar"><div class="brand-aside-fill" style="width:' + gPct + '%"></div></div>' + '<div class="brand-aside-pct">' + gPct + '% complete</div>'); } /* Achat — format classique (×3) ou XXL (×9) selon le bouton cliqué */ container.querySelectorAll('.shop-buy2[data-pack]').forEach(function(btn) { btn.addEventListener('click', function() { var packId = this.getAttribute('data-pack'); var xxl = this.getAttribute('data-xxl') === '1'; var result = openPack(packId, false, xxl); if (result === true) { Engine.play('Open Pack'); } else if (result === 'empty') { alert('⚠️ No cards available in this pack yet. Add cards to CARD_DB first.'); } else { alert('⚠️ Cannot open pack — check your balance or table access.'); } }); }); /* Défilement direct vers la table jouée quand on arrive depuis "🛍️ Shop" en fin de main (variable posée par la barre d'actions post-main, puis consommée une seule fois pour ne pas rejouer l'effet à chaque visite). */ if (v._shopFocusTable) { var target = container.querySelector('.shop-block[data-table="' + v._shopFocusTable + '"]'); if (target) { target.classList.add('shop-block-focus'); setTimeout(function() { target.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 60); setTimeout(function() { target.classList.remove('shop-block-focus'); }, 2200); } delete v._shopFocusTable; } })(); window.showFirstVisitTip('card_shop', { icon: '🛍️', title: 'Card Shop', body: 'Spend the tokens you earn at each table to open card packs. Each pack reveals 3 animated adult cards.', bodyScarlett: 'Spend your tokens wisely. Every pack you open brings you closer to completing my collection — and Legendaries are the hardest ones to pull.', bodyTrans: 'This is where your tokens turn into treasures. Every pack is a little surprise, and the Legendaries are the ones that make my heart race.', tips: [ '🎫 Tokens are earned by winning hands', '🎴 Each pack reveals 3 cards with random rarity', '⚪🔵🟣🌟 Common · Rare · Epic · Legendary', '📦 Each table has its own exclusive pack' ] }); <</script>> <</done>>
<div class="casino-main casino-lobby"> <div id="casino-brand-bar"></div> <div id="tables-container"></div> </div> <<done>> <<script>> (function() { var v = State.variables; /* ── Vérification des déblocages de tables au chargement du Casino ── */ if (v.activeBranch === 'hetero' || !v.activeBranch) { if (v.earnedHetero >= 5000 && v.unlockedTables.indexOf('boudoir') === -1) v.unlockedTables.push('boudoir'); if (v.earnedHetero >= 20000 && v.unlockedTables.indexOf('heat') === -1) v.unlockedTables.push('heat'); if (v.earnedHetero >= 30000 && v.unlockedTables.indexOf('slutpit') === -1) v.unlockedTables.push('slutpit'); if (v.earnedHetero >= 75000 && v.unlockedTables.indexOf('heterovip') === -1) v.unlockedTables.push('heterovip'); } if (v.gayUnlocked) { if (v.earnedGay >= 30000 && v.unlockedTablesGay.indexOf('gayvip') === -1) v.unlockedTablesGay.push('gayvip'); } if (v.transUnlocked) { if (v.earnedTrans >= 30000 && v.unlockedTablesTrans.indexOf('transvip') === -1) v.unlockedTablesTrans.push('transvip'); } if (v.unlockedTables.indexOf('heterovip') !== -1 && v.gayUnlocked && v.unlockedTablesGay.indexOf('gayvip') !== -1 && v.totalEarned >= 200000) { if (v.unlockedTables.indexOf('penthouse') === -1) v.unlockedTables.push('penthouse'); } var container = document.getElementById('tables-container'); if (!container) return; /* World title */ var brandBar = document.getElementById('casino-brand-bar'); if (brandBar && window.branchBanner) { var _be = v.activeBranch === 'gay' ? (v.earnedGay || 0) : v.activeBranch === 'trans' ? (v.earnedTrans || 0) : (v.earnedHetero || 0); brandBar.innerHTML = window.branchBanner('Casino', '<div class="brand-aside-label">Earned</div>' + '<div class="brand-aside-value">' + formatMoney(_be) + '</div>'); } /* Image d'ambiance par table. Sert de bandeau en haut de chaque tuile. */ var TABLE_ART = { 'vice': 'Images/icons/icon_thevice.png', 'boudoir': 'Images/icons/icon_boudoir.png', 'heat': 'Images/icons/icon_heat.png', 'slutpit': 'Images/icons/icon_slutpit.png', 'heterovip': 'Images/icons/icon_heterovip.png', 'rainbow': 'Images/icons/icon_rainbow.png', 'gayvip': 'Images/icons/icon_gayvip.png', 'transroom': 'Images/icons/icon_transroom.png', 'velvet': 'Images/icons/icon_velvet.png', 'dollhouse': 'Images/icons/icon_dollhouse.png', 'herturn': 'Images/icons/icon_herturn.png', 'trance': 'Images/icons/icon_trance.png', 'transvip': 'Images/icons/icon_transvip.png', 'penthouse': 'Images/icons/icon_penthouse.png' }; /* ══════════════════════════════════════════ INFO-BULLE DE TABLE — détail de la collection ══════════════════════════════════════════ */ /* Catégories couvertes par une table = union des catégories de tous ses packs */ function tableCategories(tableId) { var tags = []; (window.PACKS || []).forEach(function(p) { if (p.table !== tableId) return; p.categories.forEach(function(tag) { if (tags.indexOf(tag) === -1) tags.push(tag); }); }); return tags; } function categoryLabel(tag) { var c = (window.CATEGORIES || []).find(function(x) { return x.tag === tag; }); return c ? c.label : tag; } /* Détention par catégorie + total de la table */ function tableStats(tableId) { var col = v.collection || {}; var rows = tableCategories(tableId).map(function(tag) { var cards = window.CARD_DB.filter(function(c) { return c.category === tag; }); var owned = cards.filter(function(c) { return (col[c.id] || 0) > 0; }).length; return { label: categoryLabel(tag), owned: owned, total: cards.length }; }); return { rows: rows, owned: rows.reduce(function(s, r) { return s + r.owned; }, 0), total: rows.reduce(function(s, r) { return s + r.total; }, 0) }; } function buildTipHTML(t) { var st = tableStats(t.id); var pct = st.total > 0 ? Math.round(st.owned / st.total * 100) : 0; /* Au-delà de 8 catégories (VIP, Penthouse) : deux colonnes, sinon la bulle déborde */ var wide = st.rows.length > 8; var body; if (st.total === 0) { body = '<div class="ttip-empty">No cards available yet — content coming soon.</div>'; } else { body = '<div class="ttip-rows' + (wide ? ' ttip-rows-2col' : '') + '">' + st.rows.map(function(r) { var p = r.total > 0 ? Math.round(r.owned / r.total * 100) : 0; var cls = r.total === 0 ? ' ttip-row-empty' : (p === 100 ? ' ttip-row-full' : ''); return '<div class="ttip-row' + cls + '">' + '<span class="ttip-cat">' + r.label + '</span>' + '<span class="ttip-count">' + r.owned + '<em>/' + r.total + '</em></span>' + '<span class="ttip-bar"><span class="ttip-bar-fill" style="width:' + p + '%"></span></span>' + '<span class="ttip-pct">' + p + '%</span>' + '</div>'; }).join('') + '</div>'; } /* Table sans aucune carte : le total ferait doublon avec le message */ var total = st.total === 0 ? '' : '<div class="ttip-total">' + '<span class="ttip-total-label">Table completion</span>' + '<span class="ttip-total-count">' + st.owned + ' / ' + st.total + '</span>' + '<span class="ttip-total-bar"><span class="ttip-total-fill" style="width:' + pct + '%"></span></span>' + '<span class="ttip-total-pct">' + pct + '%</span>' + '</div>'; return { wide: wide, html: '<div class="ttip-head">' + '<span class="ttip-emoji">' + t.emoji + '</span>' + '<span class="ttip-title">' + t.name + '</span>' + '</div>' + body + total }; } var tipEl = null; function hideTip() { if (tipEl && tipEl.parentNode) tipEl.parentNode.removeChild(tipEl); tipEl = null; } function showTip(t, anchor) { hideTip(); var built = buildTipHTML(t); tipEl = document.createElement('div'); tipEl.className = 'table-tip' + (built.wide ? ' table-tip-wide' : ''); tipEl.innerHTML = built.html; document.body.appendChild(tipEl); /* Placement : à droite de la tuile, sinon à gauche, sinon dessous */ var r = anchor.getBoundingClientRect(); var tw = tipEl.offsetWidth; var th = tipEl.offsetHeight; var gap = 12; var side = 'right'; var left = r.right + gap; if (left + tw > window.innerWidth - 8) { left = r.left - gap - tw; side = 'left'; } if (left < 8) { left = Math.max(8, Math.min(window.innerWidth - tw - 8, r.left + r.width / 2 - tw / 2)); side = 'below'; } var top = (side === 'below') ? r.bottom + gap : r.top + r.height / 2 - th / 2; top = Math.max(8, Math.min(window.innerHeight - th - 8, top)); tipEl.style.left = Math.round(left) + 'px'; tipEl.style.top = Math.round(top) + 'px'; tipEl.classList.add('table-tip-visible'); } /* Nettoyage : bulle orpheline d'un rendu précédent + fermeture au scroll */ var staleTip = document.querySelector('.table-tip'); if (staleTip) staleTip.remove(); var noHover = window.matchMedia && window.matchMedia('(hover: none)').matches; window.addEventListener('scroll', hideTip, true); $(document).one(':passagestart', function() { hideTip(); window.removeEventListener('scroll', hideTip, true); }); function renderTable(t, isUnlocked, earned) { /* Slut Pit et Hetero VIP : contenu pas encore prêt, on bloque l'accès /* Hetero VIP et Penthouse : contenu pas encore prêt, verrouillées même si le seuil d'argent est atteint (au lieu de laisser la table s'ouvrir sur un pack vide). Slut Pit a du contenu partiel et est débloquable normalement. À ajuster avec le Card Shop. */ var FORCED_COMING_SOON = ['heterovip', 'penthouse', 'transvip', 'velvet', 'dollhouse', 'herturn', 'trance']; if (FORCED_COMING_SOON.indexOf(t.id) !== -1) isUnlocked = false; var card = document.createElement('div'); if (isUnlocked) card.id = 'tbl-' + t.id; var CASINO_CS = ['heat','slutpit','heterovip','rainbow','gayvip','transvip','penthouse']; var isCasinoCS = !isUnlocked && (FORCED_COMING_SOON.indexOf(t.id) !== -1 || (CASINO_CS.indexOf(t.id) !== -1 && t.threshold === 0)); /* Tables a venir dont on ne devoile ni le nom ni le visuel. */ var isMystery = !isUnlocked && window.mysteryTables().indexOf(t.id) !== -1; var isVip = (t.id === 'heterovip' || t.id === 'gayvip' || t.id === 'transvip' || t.id === 'penthouse'); var art = TABLE_ART[t.id] || ''; var tokens = State.variables.tokens ? (State.variables.tokens[t.id] || 0) : 0; card.className = 'table-tile' + (isUnlocked ? '' : ' table-tile-locked') + (isMystery ? ' table-tile-mystery' : ''); /* ── Tuile scellee : ni nom, ni visuel ── */ if (isMystery) { var TEASERS = { velvet: 'Softer than it looks. Not yet.', dollhouse: 'The house is not ready to show this one.', herturn: 'Patience. She decides when.', trance: 'Look away. You are not ready.', transvip: 'Behind the velvet rope. Earn your way in.' }; var teaser = TEASERS[t.id] || 'This table keeps its secrets, for now.'; card.innerHTML = '<div class="tile-media myst-media">' + '<div class="myst-pattern"></div>' + '<span class="tile-badge tile-badge-locked">🔒</span>' + '<div class="myst-mark">?</div>' + '</div>' + '<div class="tile-foot">' + '<div class="myst-ribbon">Coming soon</div>' + '<div class="myst-teaser">' + teaser + '</div>' + '</div>'; return card; } /* ── Bandeau visuel ── */ var media = '<div class="tile-media">'; if (art) { media += '<img class="tile-art" src="' + art + '" alt="' + t.name + '"' + ' onerror="this.style.display=\'none\'">'; } media += '<div class="tile-scrim"></div>'; if (isUnlocked) { media += isVip ? '<span class="tile-badge tile-badge-vip">VIP</span>' : '<span class="tile-badge tile-badge-open">Open</span>'; } else { media += '<span class="tile-badge tile-badge-locked">🔒</span>'; } media += '<div class="tile-name">' + t.name + '</div>'; media += '</div>'; /* ── Pied de tuile ── */ var foot = '<div class="tile-foot">'; if (isUnlocked) { var cst = tableStats(t.id); var cpct = cst.total > 0 ? Math.round(cst.owned / cst.total * 100) : 0; foot += '<div class="tile-foot-row">' + '<span class="tile-tokens">' + tokens + ' <em>tokens</em></span>' + (cst.total > 0 ? '<span class="tile-collect">🎴 ' + cst.owned + '/' + cst.total + ' <em>' + cpct + '%</em></span>' : '') + '</div>' + '<div class="tile-cta">Play <span class="tile-cta-arrow">→</span></div>'; } else if (isCasinoCS) { foot += '<div class="tile-cs">Coming soon</div>'; } else { var pct = t.threshold > 0 ? Math.min(100, Math.round(earned / t.threshold * 100)) : 100; foot += '<div class="tile-progress-head">' + '<span class="tile-earned">' + formatMoney(earned) + '</span>' + '<span class="tile-goal">' + formatMoney(t.threshold) + '</span>' + '</div>' + '<div class="tile-bar"><div class="tile-bar-fill" style="width:' + pct + '%"></div></div>' + '<div class="tile-unlock-note">Unlocks at <strong>' + formatMoney(t.threshold) + '</strong> earned</div>'; } foot += '</div>'; card.innerHTML = media + foot; /* Info-bulle au survol — également sur les tables verrouillées (aperçu du contenu) */ if (!noHover) { card.addEventListener('mouseenter', function() { showTip(t, card); }); card.addEventListener('mouseleave', hideTip); } if (isUnlocked) { card.addEventListener('click', function() { v.activeTable = t.id; v.activeBranch = t.branch === 'both' ? v.activeBranch : t.branch; v.gameState = 'betting'; Engine.play('Blackjack Table'); }); } return card; } if (v.activeBranch === 'hetero') { var sec = document.createElement('div'); sec.className = 'branch-section'; var grid = document.createElement('div'); grid.className = 'tables-grid'; TABLES.filter(function(t) { return t.branch === 'hetero'; }).forEach(function(t) { grid.appendChild(renderTable(t, v.unlockedTables.indexOf(t.id) !== -1, v.earnedHetero)); }); sec.appendChild(grid); container.appendChild(sec); } else if (v.activeBranch === 'gay') { var sec = document.createElement('div'); sec.className = 'branch-section'; var grid = document.createElement('div'); grid.className = 'tables-grid'; TABLES.filter(function(t) { return t.branch === 'gay'; }).forEach(function(t) { grid.appendChild(renderTable(t, v.unlockedTablesGay.indexOf(t.id) !== -1, v.earnedGay)); }); sec.appendChild(grid); container.appendChild(sec); } else if (v.activeBranch === 'trans') { var sec = document.createElement('div'); sec.className = 'branch-section'; var grid = document.createElement('div'); /* 6 tables : disposition sur deux rangees de trois */ grid.className = 'tables-grid tables-grid-3col'; TABLES.filter(function(t) { return t.branch === 'trans'; }).forEach(function(t) { grid.appendChild(renderTable(t, v.unlockedTablesTrans.indexOf(t.id) !== -1, v.earnedTrans)); }); sec.appendChild(grid); container.appendChild(sec); } var pentTable = TABLES.find(function(t) { return t.id === 'penthouse'; }); if (pentTable) { var pentUnlocked = v.unlockedTables.indexOf('penthouse') !== -1; var pentSec = document.createElement('div'); pentSec.className = 'branch-section'; pentSec.innerHTML = '<div class="branch-header penthouse-header">💎 The Penthouse — Ultimate VIP</div>'; var pentGrid = document.createElement('div'); pentGrid.className = 'tables-grid'; pentGrid.appendChild(renderTable(pentTable, pentUnlocked, v.totalEarned)); pentSec.appendChild(pentGrid); container.appendChild(pentSec); } })(); window.showFirstVisitTip('casino', { icon: '🎰', title: 'Welcome to the Casino', body: 'This is your hub. Choose a table and start playing Blackjack to earn money and tokens.', bodyScarlett: 'Welcome. I\'m Scarlett — your guide through The 21 Desires. Pick a table, place your bet, and let\'s see what the cards have in store for you tonight.', bodyTrans: 'Hi gorgeous, I\'m Coco. Welcome to my side of the house. Pick a table, place your bet, and let\'s have some fun tonight.', tips: [ '🃏 Click a table to start a new hand', '🎫 Each table has its own token pool', '🔒 New tables unlock as you earn more money', '✦ Coming Soon tables are still in development' ] }); /* ── TIPS DE DÉBLOQUAGE DE TABLE ── */ (function() { var v = State.variables; if (v.activeBranch === 'hetero' && v.tableUnlockSeen) { var TABLE_UNLOCK_TIPS = { boudoir: { title: 'New Table Unlocked — The Boudoir 💋', bodyScarlett: 'You\'ve earned your way into The Boudoir. Things get a little more... intimate here. New packs, new content. Don\'t be shy.', tips: [ '💋 The Boudoir has its own exclusive card packs', '🎫 Earn tokens here by winning hands at this table', '🔞 More explicit content than The Vice', '🎴 New categories unlocked in this table\'s packs' ] }, heat: { title: 'New Table Unlocked — The Heat 🔥', bodyScarlett: 'The Heat. You\'ve come a long way. The cards here are something else — explicit, raw, and very hard to forget. You\'ve been warned.', tips: [ '🔥 The Heat features the most explicit categories so far', '🎴 New categories : Anal, Blowjob, Fucking and more', '🎫 Its own token pool — start earning here', '🌟 Legendary is the rarest tier — only 3% of pulls' ] }, slutpit: { title: 'New Table Unlocked — The Slut Pit 💀', bodyScarlett: 'The Slut Pit. Most players never make it this far. The content here is... not for the faint-hearted. Enjoy.', tips: [ '💀 The Slut Pit — the most extreme content in the casino', '🎴 New categories : BBC, Threesome, DP, Rimjob', '🎫 Its own token pool — spend them wisely', '🌟 Same rarity odds as everywhere — Legendary stays at 3%' ] }, heterovip: { title: 'VIP Access Unlocked — Hetero VIP 👑', bodyScarlett: 'VIP. You\'ve earned it. This room has access to every single category — and a Legendary Pack that guarantees the rarest tier every time. Welcome to the top.', tips: [ '👑 VIP Pack includes all hetero categories', '🌟 Legendary Pack available — guaranteed Legendary card', '💎 The rarest tier in the game is available here', '✦ You\'ve reached the top of the Straight branch' ] }, penthouse: { title: 'The Penthouse Unlocked 💎', bodyScarlett: 'The Penthouse. All branches, all categories, no limits. This is the highest table in the house — and it belongs to you now.', tips: [ '💎 The Penthouse draws from every category in the game', '🌟 Legendary Pack available — all categories included', '✦ The ultimate table — you\'ve unlocked everything', '🎴 Wild Card packs can drop anything' ] } }; /* Hetero VIP et Penthouse volontairement exclues : ces tables restent forcées en "Coming Soon" (FORCED_COMING_SOON), donc pas de popup "débloquée" tant qu'elles ne sont pas réellement jouables. À réintégrer le jour où leur contenu sera prêt. */ var checkTables = ['boudoir', 'heat', 'slutpit']; for (var i = 0; i < checkTables.length; i++) { var tbl = checkTables[i]; if ( v.unlockedTables.indexOf(tbl) !== -1 && !v.tableUnlockSeen[tbl] ) { v.tableUnlockSeen[tbl] = true; var tipCfg = TABLE_UNLOCK_TIPS[tbl]; (function(cfg) { setTimeout(function() { window.showFirstVisitTip('table_unlock_' + tbl, { icon: '🔓', title: cfg.title, body: cfg.title, bodyScarlett: cfg.bodyScarlett, tips: cfg.tips }); }, 800); })(tipCfg); break; /* Un seul tip à la fois */ } } } })(); <</script>> <</done>>
<div class="credits-page"> <div class="credits-content"> <div class="credits-logo-wrap"> <img src="Images/credits/credit_logo.png" class="credits-logo" alt="Draggora Studio Logo"> </div> <div class="credits-studio-name">DRAGGORA STUDIO</div> <div class="credits-tagline">Independent Adult Game Developer</div> <div class="credits-divider"></div> <div class="credits-section"> <div class="credits-section-title">🎮 The 21 Desires</div> <div class="credits-game-info"> <span class="credits-version">Version <<print window.GAME_VERSION>></span> <span class="credits-year">© 2026 Draggora Studio</span> </div> <p class="credits-desc">A blackjack card-collecting adult game. Play, earn, unlock and collect explicit animated cards across multiple tables and categories.</p> </div> <div class="credits-divider"></div> <div class="credits-section"> <div class="credits-section-title">🔗 Find us</div> <div class="credits-links"> <a class="credits-link credits-link-patreon" href="https://patreon.com/Draggora_Studio?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink" target="_blank"> <span class="cl-icon">🎁</span> <span class="cl-body"> <span class="cl-label">Patreon</span> <span class="cl-sub">Support the project & get early access</span> </span> <span class="cl-arrow">→</span> </a> <a class="credits-link credits-link-itch" href="https://draggora-studio.itch.io/the-21-desires" target="_blank"> <span class="cl-icon">🎮</span> <span class="cl-body"> <span class="cl-label">itch.io</span> <span class="cl-sub">Download & follow updates</span> </span> <span class="cl-arrow">→</span> </a> <a class="credits-link credits-link-mail" href="mailto:draggora_studio@outlook.fr"> <span class="cl-icon">✉️</span> <span class="cl-body"> <span class="cl-label">Contact</span> <span class="cl-sub">draggora_studio@outlook.fr</span> </span> <span class="cl-arrow">→</span> </a> </div> </div> <div class="credits-divider"></div> <div class="credits-section"> <div class="credits-section-title">Supporters</div> <div class="credits-thanks"> <p class="ct-intro">A massive thank you to the people backing this on Patreon:</p> <div class="ct-names"> <span class="ct-name">Robert</span> <span class="ct-name">Jon-David P.</span> </div> </div> </div> <div class="credits-divider"></div> <div class="credits-legal"> <p>This game contains explicit adult content intended for persons aged 18 or older. All characters depicted are fictional and over the age of 18. This is a work of fiction — any resemblance to real persons or events is purely coincidental.</p> <p>Some visual content used may belong to third parties. If you are the owner of content displayed in The 21 Desires and wish to have it removed, please contact us at <strong>draggora_studio@outlook.fr</strong>.</p> </div> </div> </div> <<done>> <<script>> (function() { /* Credits page loaded */ })(); <</script>> <</done>> <style> /* ══════════════════════════════════════════ CREDITS PAGE — redesign lisible ══════════════════════════════════════════ */ .credits-page { min-height: 100vh; display: flex; flex-direction: column; background: #06040f; padding-bottom: 80px; } .credits-content { max-width: 760px; margin: 50px auto 0; padding: 0 28px; display: flex; flex-direction: column; align-items: center; gap: 0; position: relative; z-index: 2; text-align: center; } .credits-logo-wrap { margin-bottom: 26px; } .credits-logo { width: 220px; height: 220px; object-fit: contain; filter: drop-shadow(0 0 40px rgba(255,45,120,0.5)); } .credits-studio-name { font-family: 'Orbitron', monospace; font-size: 2.2rem; font-weight: 900; letter-spacing: 0.2em; background: linear-gradient(135deg, #ffd700, #ff4477, #ffd700); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 12px; } .credits-tagline { font-family: 'Share Tech Mono', monospace; font-size: 1rem; letter-spacing: 0.28em; color: rgba(210,200,235,0.72); text-transform: uppercase; margin-bottom: 4px; } .credits-divider { width: 100%; height: 1px; background: linear-gradient(90deg, transparent, rgba(255,215,0,0.25), transparent); margin: 36px 0; } .credits-section { width: 100%; text-align: left; } .credits-section-title { font-family: 'Orbitron', monospace; font-size: 0.95rem; font-weight: 900; letter-spacing: 0.22em; text-transform: uppercase; color: rgba(255,215,0,0.85); margin-bottom: 18px; } .credits-game-info { display: flex; gap: 14px; margin-bottom: 18px; flex-wrap: wrap; } .credits-version, .credits-year { font-family: 'Share Tech Mono', monospace; font-size: 0.95rem; letter-spacing: 0.1em; color: rgba(210,200,235,0.85); padding: 7px 18px; border: 1px solid rgba(255,255,255,0.14); border-radius: 20px; background: rgba(255,255,255,0.05); } .credits-desc { font-family: 'Rajdhani', sans-serif; font-size: 1.2rem; font-weight: 500; color: rgba(225,215,245,0.88); line-height: 1.75; margin: 0; } /* Links */ .credits-links { display: flex; flex-direction: column; gap: 14px; } .credits-link { display: flex; align-items: center; gap: 18px; padding: 19px 22px; border-radius: 14px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.04); text-decoration: none !important; transition: all 0.2s; cursor: pointer; } .credits-link:hover { transform: translateX(4px); border-color: rgba(255,255,255,0.2); } .credits-link-patreon:hover { border-color: rgba(255,105,0,0.5); background: rgba(255,105,0,0.08); } .credits-link-itch:hover { border-color: rgba(255,45,120,0.5); background: rgba(255,45,120,0.08); } .credits-link-mail:hover { border-color: rgba(0,245,255,0.45); background: rgba(0,245,255,0.06); } .cl-icon { font-size: 1.9rem; flex-shrink: 0; } .cl-body { display: flex; flex-direction: column; gap: 4px; flex: 1; } .cl-label { font-family: 'Orbitron', monospace; font-size: 1rem; font-weight: 700; letter-spacing: 0.08em; color: #ffffff; } .cl-sub { font-family: 'Rajdhani', sans-serif; font-size: 1.05rem; font-weight: 500; color: rgba(210,200,235,0.75); } .cl-arrow { font-family: 'Share Tech Mono', monospace; font-size: 1.15rem; color: rgba(255,255,255,0.35); flex-shrink: 0; } /* Legal */ .credits-legal { width: 100%; padding: 22px 24px; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.07); border-radius: 12px; } .credits-legal p { font-family: 'Rajdhani', sans-serif; font-size: 1.02rem; color: rgba(210,200,235,0.65); line-height: 1.75; margin: 0 0 12px; } .credits-legal p:last-child { margin: 0; } .credits-legal strong { color: rgba(210,200,235,0.9); } </style>
<<silently>> <<set $playerHand = []>> <<set $dealerHand = []>> <<set $isSplit = false>> <<set $splitHand = []>> <<set $splitBet = 0>> <<set $currentHand = "main">> <<set $gameState = "playing">> <<set $playerMoney -= $currentBet>> <<set $playerHand.push(drawCard())>> <<set $dealerHand.push(drawCard())>> <<set $playerHand.push(drawCard())>> <<set $dealerHand.push(drawCard())>> <<set $canDouble = ($playerMoney >= $currentBet)>> <<set $canSplit = (canSplit($playerHand) && $playerMoney >= $currentBet)>> <</silently>> <<goto "Play Hand">>
<div class="game-layout"> <div class="game-table"> <div class="dealer-zone"> <div class="zone-label dealer-label"><span class="zl-icon">🤖</span><span class="zl-text">Dealer — Drawing</span></div> <<print displayHand($dealerHand, false)>> <<set _dv = calculateHand($dealerHand)>> <div class="hand-total">Total : <<print _dv>></div> <<if _dv < 17>><p class="dealer-thinking">⟳ dealer drawing a card...</p><</if>> </div> <div class="player-zone"> <div class="zone-label player-label"><span class="zl-icon">🎭</span><span class="zl-text">Your main hand</span></div> <<print displayHand($playerHand, false)>> <div class="hand-total">Total : <<print calculateHand($playerHand)>></div> <<if $isSplit>> <div class="split-zone" style="margin-top:10px;"> <div class="zone-label player-label"><span class="zl-icon">✂️</span><span class="zl-text">Your Split hand</span></div> <<print displayHand($splitHand, false)>> <div class="hand-total">Total : <<print calculateHand($splitHand)>></div> </div> <</if>> </div> </div> </div> <<set _dv = calculateHand($dealerHand)>> <<if _dv < 17>> <<set $dealerHand.push(drawCard())>> <<timed 1s>><<goto "Dealer Turn">><</timed>> <<else>> <<set $gameState = "result">> <<timed 1.2s>><<goto "Game Result">><</timed>> <</if>> <<done>> <<script>> (function() { var old = document.getElementById('game-hud'); if (old) old.remove(); var v = State.variables; var hud = document.createElement('div'); hud.id = 'game-hud'; document.getElementById('passages').classList.add('game-page'); hud.innerHTML = '<div class="hud-logo">THE <span>21</span> DESIRES</div>' + '<div class="hud-stat"><span class="hud-stat-label">\u{1F4B0} Balance</span><span class="hud-stat-value">' + formatMoney(v.playerMoney) + '</span></div>' + '<div class="hud-sep"></div>' + '<div class="hud-stat"><span class="hud-stat-label">\u{1F3B0} Bet</span><span class="hud-stat-value bet-color">' + formatMoney(v.currentBet) + '</span></div>' + '<div class="hud-sep"></div>' + '<div class="hud-sep"></div>' + '<div class="hud-stat"><span class="hud-stat-label">\u{1F3AB} Tokens</span><span class="hud-stat-value" style="color:var(--token);text-shadow:0 0 10px var(--token-glow);">' + (v.tokens ? (v.tokens[v.activeTable] || 0) : 0) + '</span></div>' + '<div class="hud-sep"></div>' + '<div class="hud-stat"><span class="hud-stat-label">Turn</span><span class="hud-stat-value" style="font-size:0.95rem;color:var(--neon-purple);animation:blink 1s step-end infinite;">Dealer</span></div>' + '<div class="hud-sep"></div><div class="hud-stat"><span class="hud-stat-label">🔥 Streak</span><span class="hud-stat-value" style="color:' + ((v.currentStreak && v.currentStreak[v.activeTable] > 0) ? 'var(--neon-pink);text-shadow:0 0 10px rgba(255,45,120,0.5)' : 'rgba(255,255,255,0.3)') + ';">' + ((v.currentStreak && v.currentStreak[v.activeTable]) || 0) + '</span></div>'; document.body.appendChild(hud); var _TBG = { 'vice': 'Images/icons/icon_thevice.png', 'boudoir': 'Images/icons/icon_boudoir.png', 'heat': 'Images/icons/icon_heat.png', 'slutpit': 'Images/icons/icon_slutpit.png', 'heterovip': 'Images/icons/icon_heterovip.png', 'rainbow': 'Images/scenes/the_rainbow.png', 'gayvip': 'Images/scenes/the_gay_vip.png', 'transroom': 'Images/icons/icon_transroom.png', 'velvet': 'Images/icons/icon_velvet.png', 'dollhouse': 'Images/icons/icon_dollhouse.png', 'herturn': 'Images/icons/icon_herturn.png', 'trance': 'Images/icons/icon_trance.png', 'transvip': 'Images/icons/icon_transvip.png', 'penthouse': 'Images/scenes/the_penthouse.png' }; (function applyTableBg() { var at = State.variables.activeTable; var img = at && _TBG[at] ? _TBG[at] : null; document.body.style.backgroundImage = img ? 'linear-gradient(rgba(0,0,0,0.62),rgba(0,0,0,0.55),rgba(0,0,0,0.62)), url("' + img + '")' : ''; document.body.style.backgroundSize = 'cover'; document.body.style.backgroundPosition = 'center'; document.body.style.backgroundAttachment = 'fixed'; })(); })(); <</script>> <</done>>
<div class="go2-screen" id="go2-screen"> <div class="go2-skull">💀</div> <h1 class="go2-title">GAME OVER</h1> <p class="go2-sub" id="go2-sub">You ran out of money.</p> <p class="go2-scope" id="go2-scope"></p> <div class="go2-penalty-box" id="go2-penalty-box"> <div class="go2-penalty-title">⚠️ Penalty applied</div> <div class="go2-penalty-rows" id="go2-penalty-rows"> <div class="go2-penalty-row"> <span class="go2-penalty-label">Cards lost</span> <span class="go2-penalty-val go2-val-red" id="go2-cards-lost">—</span> </div> <div class="go2-penalty-row"> <span class="go2-penalty-label">Tokens lost</span> <span class="go2-penalty-val go2-val-red" id="go2-tokens-lost">—</span> </div> <div class="go2-penalty-row"> <span class="go2-penalty-label">Starting balance</span> <span class="go2-penalty-val go2-val-green">$1,500 restored</span> </div> </div> </div> <div class="go2-warning-box" id="go2-warning-box"> <span class="go2-warning-icon" id="go2-warning-icon">⚡</span> <div> <div class="go2-warning-title" id="go2-warning-title">Warning — penalties escalate</div> <div class="go2-warning-body" id="go2-warning-body"></div> </div> </div> <div class="go2-actions"> <a class="btn btn-primary go2-btn" id="go2-try-again">▶ Try Again</a> </div> </div> <<done>> <<script>> (function() { var v = State.variables; /* Les mondes sont cloisonnes : tout ce qui suit ne concerne QUE la branche ou le joueur s'est ruine. */ var BR = v.activeBranch || 'hetero'; /* Catégories et tables appartenant a cette branche */ var BR_CATS = (window.CATEGORIES || []) .filter(function(c) { return c.branch === BR; }) .map(function(c) { return c.tag; }); var BR_TABLES = (window.TABLES || []) .filter(function(tb) { return tb.branch === BR; }) .map(function(tb) { return tb.id; }); /* ── 1. Incrémenter le compteur de CETTE branche ── */ if (typeof v.gameOverCount !== 'object' || v.gameOverCount === null) { v.gameOverCount = { hetero:0, gay:0, trans:0 }; } v.gameOverCount[BR] = (v.gameOverCount[BR] || 0) + 1; var level = Math.min(v.gameOverCount[BR], 5); /* ── 2. Fraction de perte ── */ var fracs = { 1: 1/4, 2: 1/3, 3: 1/2, 4: 3/4, 5: 1 }; var frac = fracs[level] || 1; var labels = { 1:'1/4', 2:'1/3', 3:'1/2', 4:'3/4', 5:'ALL' }; var penaltyLabel = labels[level]; var nextLabel = labels[Math.min(level + 1, 5)] || 'ALL'; /* ── 3. Supprimer les cartes ── */ var col = v.collection || {}; /* Seules les cartes de cette branche sont exposees a la perte */ var cardBranch = {}; (window.CARD_DB || []).forEach(function(c) { cardBranch[c.id] = c.category; }); var ownedIds = Object.keys(col).filter(function(id) { return col[id] > 0 && BR_CATS.indexOf(cardBranch[id]) !== -1; }); var totalOwned = ownedIds.length; /* Fisher-Yates shuffle */ var shuffled = ownedIds.slice(); for (var i = shuffled.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var tmp = shuffled[i]; shuffled[i] = shuffled[j]; shuffled[j] = tmp; } var toRemove = level >= 5 ? totalOwned : Math.floor(totalOwned * frac); for (var k = 0; k < toRemove; k++) { if (shuffled[k]) delete col[shuffled[k]]; } /* ── 4. Réduire les tokens ── */ if (!v.tokens) v.tokens = {}; Object.keys(v.tokens).forEach(function(tkey) { if (BR_TABLES.indexOf(tkey) === -1) return; /* autres mondes epargnes */ v.tokens[tkey] = Math.max(0, Math.floor(v.tokens[tkey] * (1 - frac))); }); /* ── 5. Reset argent + stats + lucky wheel ── */ v.playerMoney = 1500; if (!v.branchMoney) v.branchMoney = { hetero:1500, gay:1500, trans:1500 }; v.branchMoney[BR] = 1500; /* la bourse des autres mondes est intacte */ v.totalWins = 0; v.totalLosses = 0; v.totalPushes = 0; v.totalHandsPlayed = 0; v.biggestWin = 0; v.wheelHandsCount[BR] = 0; v.wheelReady[BR] = false; /* Seules les series des tables de cette branche sont remises a zero */ if (!v.currentStreak) v.currentStreak = {}; BR_TABLES.forEach(function(tid) { v.currentStreak[tid] = 0; }); if (v.lossStreak) BR_TABLES.forEach(function(tid) { v.lossStreak[tid] = 0; }); v.consolationPending = null; /* ── 6. Mettre à jour l'affichage ── */ /* Rappeler explicitement que seul ce monde est touche */ var scopeEl = document.getElementById('go2-scope'); var brInfo = (window.BRANCH_INFO && window.BRANCH_INFO[BR]) || null; var brName = brInfo ? brInfo.label : BR; if (scopeEl) { scopeEl.innerHTML = 'This only affects the <strong>' + brName + '</strong> world. ' + 'Your balance, cards and tokens in the other worlds are untouched.'; } var cardsEl = document.getElementById('go2-cards-lost'); var tokensEl = document.getElementById('go2-tokens-lost'); var warnBody = document.getElementById('go2-warning-body'); var warnIcon = document.getElementById('go2-warning-icon'); var warnBox = document.getElementById('go2-warning-box'); var warnTitle= document.getElementById('go2-warning-title'); if (cardsEl) cardsEl.textContent = '−' + toRemove + ' / ' + totalOwned + ' (' + penaltyLabel + ')'; if (tokensEl) tokensEl.textContent = '−' + penaltyLabel + ' of ' + brName + ' tokens'; var isMax = (level >= 5); if (warnBody) { if (isMax) { warnBody.innerHTML = 'Any future game over in ' + brName + ' will wipe your <strong>entire ' + brName + ' collection</strong> and all its tokens. Play carefully.'; } else { warnBody.innerHTML = 'Next game over in ' + brName + ' will cost you <strong>' + nextLabel + '</strong> of your ' + brName + ' collection and tokens. Manage your bankroll carefully.'; } } if (isMax && warnBox) warnBox.classList.add('go2-warning-max'); if (isMax && warnIcon) warnIcon.textContent = '☠️'; if (isMax && warnTitle) warnTitle.textContent = 'Maximum penalty reached'; /* ── 7. Désactiver tous les boutons sidebar sauf Try Again ── */ var sidebar = document.getElementById('nav-sidebar'); if (sidebar) { sidebar.querySelectorAll('a, button').forEach(function(el) { el.style.pointerEvents = 'none'; el.style.opacity = '0.25'; el.style.cursor = 'not-allowed'; }); } /* ── 8. Listener Try Again ── */ var btn = document.getElementById('go2-try-again'); if (btn) { btn.style.pointerEvents = ''; btn.style.opacity = ''; btn.style.cursor = ''; btn.addEventListener('click', function() { Engine.play('Casino'); }); } if (typeof buildNavSidebar === 'function') buildNavSidebar(); /* Re-désactiver après rebuild sidebar */ setTimeout(function() { var sb2 = document.getElementById('nav-sidebar'); if (sb2) { sb2.querySelectorAll('a, button').forEach(function(el) { el.style.pointerEvents = 'none'; el.style.opacity = '0.25'; el.style.cursor = 'not-allowed'; }); } }, 50); })(); <</script>> <</done>> <style> /* ══════════════════════════════════════════ GAME OVER PAGE ══════════════════════════════════════════ */ .go2-screen { min-height: 80vh; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 22px; padding: 40px 20px; max-width: 560px; margin: 0 auto; text-align: center; } .go2-skull { font-size: 4rem; filter: drop-shadow(0 0 20px rgba(255,51,80,0.6)); animation: goShake 0.6s ease-out; } @keyframes goShake { 0%,100% { transform: translateX(0) rotate(0); } 20% { transform: translateX(-10px) rotate(-5deg); } 40% { transform: translateX(10px) rotate(5deg); } 60% { transform: translateX(-6px) rotate(-3deg); } 80% { transform: translateX(6px) rotate(3deg); } } .go2-title { font-family: 'Orbitron', monospace; font-size: clamp(2rem, 6vw, 3rem); font-weight: 900; color: #ff3350; letter-spacing: 0.15em; text-shadow: 0 0 30px rgba(255,51,80,0.5), 0 0 60px rgba(255,51,80,0.2); margin: 0; } .go2-scope { max-width: 520px; margin: 10px auto 0; text-align: center; font-family: 'Rajdhani', sans-serif; font-size: 1.02rem; line-height: 1.55; color: rgba(210,200,235,0.72); padding: 12px 18px; border-radius: 10px; background: rgba(0,245,255,0.05); border: 1px solid rgba(0,245,255,0.18); } .go2-scope strong { color: var(--neon-cyan); font-weight: 700; } .go2-sub { font-family: 'Rajdhani', sans-serif; font-size: 1.1rem; color: rgba(210,200,235,0.5); letter-spacing: 0.1em; margin: 0; } .go2-penalty-box { width: 100%; background: rgba(255,51,80,0.06); border: 1px solid rgba(255,51,80,0.25); border-radius: 16px; padding: 20px 24px; display: flex; flex-direction: column; gap: 14px; } .go2-penalty-title { font-family: 'Orbitron', monospace; font-size: 0.72rem; font-weight: 900; letter-spacing: 0.2em; text-transform: uppercase; color: rgba(255,51,80,0.7); text-align: left; padding-bottom: 10px; border-bottom: 1px solid rgba(255,51,80,0.12); } .go2-penalty-rows { display: flex; flex-direction: column; gap: 10px; } .go2-penalty-row { display: flex; justify-content: space-between; align-items: center; } .go2-penalty-label { font-family: 'Rajdhani', sans-serif; font-size: 0.95rem; font-weight: 600; color: rgba(210,200,235,0.6); } .go2-penalty-val { font-family: 'Share Tech Mono', monospace; font-size: 0.9rem; font-weight: bold; } .go2-val-red { color: #ff3350; text-shadow: 0 0 10px rgba(255,51,80,0.4); } .go2-val-green { color: #00f5a0; text-shadow: 0 0 10px rgba(0,245,160,0.3); } .go2-warning-box { width: 100%; display: flex; align-items: flex-start; gap: 14px; padding: 16px 20px; background: rgba(255,200,0,0.05); border: 1px solid rgba(255,200,0,0.2); border-radius: 14px; text-align: left; } .go2-warning-max { background: rgba(255,51,80,0.06) !important; border-color: rgba(255,51,80,0.25) !important; } .go2-warning-icon { font-size: 1.6rem; flex-shrink: 0; } .go2-warning-title { font-family: 'Orbitron', monospace; font-size: 0.72rem; font-weight: 900; letter-spacing: 0.12em; text-transform: uppercase; color: rgba(255,200,0,0.8); margin-bottom: 6px; } .go2-warning-max .go2-warning-title { color: rgba(255,80,80,0.8); } .go2-warning-body { font-family: 'Rajdhani', sans-serif; font-size: 0.95rem; font-weight: 500; color: rgba(210,200,235,0.65); line-height: 1.5; } .go2-warning-body strong { color: rgba(255,200,0,0.9); } .go2-warning-max .go2-warning-body strong { color: #ff3350; } .go2-actions { margin-top: 8px; } .go2-btn { padding: 16px 56px !important; font-size: 1rem !important; letter-spacing: 0.15em !important; } </style>
<<silently>> <<set _dv = calculateHand($dealerHand)>> <<set _pv = calculateHand($playerHand)>> <<set _pb = (_pv > 21)>> <<set _db = (_dv > 21)>> <<set _pBJ = isBlackjack($playerHand)>> <<set _dBJ = isBlackjack($dealerHand)>> <<set _totalWin = 0>> <<set $lastResultBlackjack = false>> <<if _pb>> <<set _mainResult = "bust">> <<elseif _pBJ && !_dBJ>> <<set _mainResult = "blackjack">> <<set _totalWin += $currentBet + Math.floor($currentBet * 1.5)>> <<set $lastResultBlackjack = true>> <<elseif _pBJ && _dBJ>> <<set _mainResult = "push">> <<set _totalWin += $currentBet>> <<elseif _db>> <<set _mainResult = "win">> <<set _totalWin += $currentBet * 2>> <<elseif _pv > _dv>> <<set _mainResult = "win">> <<set _totalWin += $currentBet * 2>> <<elseif _pv < _dv>> <<set _mainResult = "lose">> <<else>> <<set _mainResult = "push">> <<set _totalWin += $currentBet>> <</if>> <<if $isSplit>> <<set _sv = calculateHand($splitHand)>> <<set _sb = (_sv > 21)>> <<if _sb>> <<set _splitResult = "bust">> <<elseif _db>> <<set _splitResult = "win">> <<set _totalWin += $splitBet * 2>> <<elseif _sv > _dv>> <<set _splitResult = "win">> <<set _totalWin += $splitBet * 2>> <<elseif _sv < _dv>> <<set _splitResult = "lose">> <<else>> <<set _splitResult = "push">> <<set _totalWin += $splitBet>> <</if>> <</if>> <<set $playerMoney += _totalWin>> <<set $totalHandsPlayed += 1>> /* Lucky Wheel — incrément compteur */ <<if !$wheelReady[$activeBranch]>> <<set $wheelHandsCount[$activeBranch] += 1>> <<if $wheelHandsCount[$activeBranch] >= 20>><<set $wheelReady[$activeBranch] = true>><</if>> <</if>> <<if _totalWin > 0>> <<set $totalEarned += _totalWin>> /* Incrémenter le compteur de la bonne branche */ <<if $activeBranch === "gay">> <<set $earnedGay += _totalWin>> <<elseif $activeBranch === "trans">> <<set $earnedTrans += _totalWin>> <<else>> <<set $earnedHetero += _totalWin>> <</if>> /* Gagner des jetons de la table active — 1 jeton par $10 gagnés */ <<set _tokensEarned = Math.floor(_totalWin / 10)>> <<if _tokensEarned > 0>> <<if !$tokens>> <<set $tokens = { vice:0, boudoir:0, heat:0, slutpit:0, heterovip:0, rainbow:0, gayvip:0, transroom:0, velvet:0, dollhouse:0, herturn:0, trance:0, transvip:0, penthouse:0 }>> <</if>> <<set $tokens[$activeTable] += _tokensEarned>> <</if>> <</if>> <<if _mainResult === "win" || _mainResult === "blackjack">> <<set $totalWins += 1>> <<elseif _mainResult === "lose" || _mainResult === "bust">> <<set $totalLosses += 1>> <<else>> <<set $totalPushes += 1>> <</if>> <<if _totalWin > $biggestWin>><<set $biggestWin = _totalWin>><</if>> <<set $lastHandResult = _mainResult>> <<set $lastHandTokens = Math.floor(_totalWin / 10)>> <<set $lastHandWinMoney = _totalWin>> <<set $lastHandCanDouble = ((_mainResult === "win") || ($isSplit && _splitResult === "win")) && _totalWin > 0>> /* Note : "blackjack" est volontairement exclu — un blackjack naturel donne déjà un pack gratuit (voir triggerBlackjackCelebration), pas de cumul avec le Double or Nothing. Un blackjack ne peut survenir que sur la main principale (jamais sur une main issue d'un split, voir plus haut), donc cette exclusion n'affecte jamais un gain split séparé et légitime. */ /* Streak */ <<if $isSplit>> <<if (_mainResult === "win" || _mainResult === "blackjack") && (_splitResult === "win" || _splitResult === "blackjack")>> <<set $currentStreak[$activeTable] = ($currentStreak[$activeTable] || 0) + 1>> <<else>> <<set $currentStreak[$activeTable] = 0>> <</if>> <<elseif _mainResult === "win" || _mainResult === "blackjack">> <<set $currentStreak[$activeTable] = ($currentStreak[$activeTable] || 0) + 1>> <<elseif _mainResult === "lose" || _mainResult === "bust">> <<set $currentStreak[$activeTable] = 0>> <</if>> /* Serie de pertes (cachee) : peut declencher un lot de consolation */ <<run window.updateLossStreak(_mainResult, $isSplit ? _splitResult : null)>> /* Déblocages branche hétéro */ <<if $earnedHetero >= 5000 && !$unlockedTables.includes("boudoir")>><<set $unlockedTables.push("boudoir")>><</if>> <<if $earnedHetero >= 20000 && !$unlockedTables.includes("heat")>><<set $unlockedTables.push("heat")>><</if>> <<if $earnedHetero >= 30000 && !$unlockedTables.includes("slutpit")>><<set $unlockedTables.push("slutpit")>><</if>> <<if $earnedHetero >= 75000 && !$unlockedTables.includes("heterovip")>><<set $unlockedTables.push("heterovip")>><</if>> /* Déblocages branche gay */ <<if $gayUnlocked>> <<if $earnedGay >= 30000 && !$unlockedTablesGay.includes("gayvip")>><<set $unlockedTablesGay.push("gayvip")>><</if>> <</if>> /* Branche trans : ouverte dès le départ (comme Straight) */ <<if !$transUnlocked>><<set $transUnlocked = true>><</if>> /* Déblocages tables trans */ <<if $transUnlocked>> <<if $earnedTrans >= 30000 && !$unlockedTablesTrans.includes("transvip")>><<set $unlockedTablesTrans.push("transvip")>><</if>> <</if>> /* Déblocage Penthouse — les deux branches VIP requises */ <<if $unlockedTables.includes("heterovip") && $gayUnlocked && $unlockedTablesGay.includes("gayvip") && $totalEarned >= 200000>> <<if !$unlockedTables.includes("penthouse")>><<set $unlockedTables.push("penthouse")>><</if>> <</if>> <</silently>> <div class="game-layout"> <div class="game-table"> <div class="dealer-zone"> <div class="zone-label dealer-label"><span class="zl-icon">🤖</span><span class="zl-text">Dealer — <<print _dv>><<if _db>> 💥 BUST<</if>></span></div> <<print displayHand($dealerHand, false)>> <div class="hand-total <<if _db>>bust<</if>>">Total : <<print _dv>><<if _dBJ>> — BLACKJACK ⭐<</if>></div> </div> <div class="player-zone"> <div class="zone-label player-label"><span class="zl-icon">🎭</span><span class="zl-text">Your hand</span></div> <<print displayHand($playerHand, false)>> <div class="hand-total">Total : <<print _pv>></div> <<if _mainResult === "blackjack">> <div class="result-banner result-blackjack">⭐ BLACKJACK!<div class="result-amount">+<<print formatMoney(Math.floor($currentBet * 1.5))>></div><div class="result-tokens">🎫 +<<print _tokensEarned>> tokens</div></div> <<elseif _mainResult === "win">> <div class="result-banner result-win">✅ YOU WIN!<div class="result-amount">+<<print formatMoney($currentBet)>></div><div class="result-tokens">🎫 +<<print _tokensEarned>> tokens</div></div> <<elseif _mainResult === "bust">> <div class="result-banner result-bust">💥 BUST!<div class="result-amount">-<<print formatMoney($currentBet)>></div></div> <<elseif _mainResult === "lose">> <div class="result-banner result-lose">❌ YOU LOSE<div class="result-amount">-<<print formatMoney($currentBet)>></div></div> <<else>> <div class="result-banner result-push">🤝 PUSH — Bet returned</div> <</if>> <<if $isSplit>> <div class="split-zone" style="margin-top:8px;"> <div class="zone-label player-label"><span class="zl-icon">✂️</span><span class="zl-text">Split hand</span></div> <<print displayHand($splitHand, false)>> <div class="hand-total">Total : <<print _sv>></div> <<if _splitResult === "win">> <div class="result-banner result-win">✅ WIN<div class="result-amount">+<<print formatMoney($splitBet)>></div><div class="result-tokens">🎫 +<<print Math.floor($splitBet * 2 / 10)>> tokens</div></div> <<elseif _splitResult === "bust">> <div class="result-banner result-bust">💥 BUST<div class="result-amount">-<<print formatMoney($splitBet)>></div></div> <<elseif _splitResult === "lose">> <div class="result-banner result-lose">❌ LOSE<div class="result-amount">-<<print formatMoney($splitBet)>></div></div> <<else>> <div class="result-banner result-push">🤝 PUSH</div> <</if>> </div> <</if>> </div> </div> <div id="dbl-nothing-zone"></div> </div> <<done>> <<script>> (function() { var old = document.getElementById('game-hud'); if (old) old.remove(); var v = State.variables; var hud = document.createElement('div'); hud.id = 'game-hud'; document.getElementById('passages').classList.add('game-page'); hud.innerHTML = '<div class="hud-logo">THE <span>21</span> DESIRES</div>' + '<div class="hud-stat"><span class="hud-stat-label">\u{1F4B0} Balance</span><span class="hud-stat-value">' + formatMoney(v.playerMoney) + '</span></div>' + '<div class="hud-sep"></div>' + '<div class="hud-stat"><span class="hud-stat-label">\u{1F3B0} Bet</span><span class="hud-stat-value bet-color">' + formatMoney(v.currentBet) + '</span></div>' + '<div class="hud-sep"></div>' + '<div class="hud-stat"><span class="hud-stat-label">\u{1F3AB} Tokens</span><span class="hud-stat-value" style="color:var(--token);text-shadow:0 0 10px var(--token-glow);">' + (v.tokens ? (v.tokens[v.activeTable] || 0) : 0) + '</span></div>' + '<div class="hud-sep"></div><div class="hud-stat"><span class="hud-stat-label">🔥 Streak</span><span class="hud-stat-value" style="color:' + ((v.currentStreak && v.currentStreak[v.activeTable] > 0) ? 'var(--neon-pink);text-shadow:0 0 10px rgba(255,45,120,0.5)' : 'rgba(255,255,255,0.3)') + ';">' + ((v.currentStreak && v.currentStreak[v.activeTable]) || 0) + '</span></div>' + '<div class="hud-actions">' + '<a class="btn btn-primary hud-btn-newhand">🎰 New Hand</a>' + '<a class="btn btn-stand hud-btn-casino">🏠 Casino</a>' + '</div>'; document.body.appendChild(hud); hud.querySelector('.hud-btn-newhand').addEventListener('click', function() { Engine.play('Blackjack Table'); }); hud.querySelector('.hud-btn-casino').addEventListener('click', function() { Engine.play('Casino'); }); var _TBG = { 'vice': 'Images/icons/icon_thevice.png', 'boudoir': 'Images/icons/icon_boudoir.png', 'heat': 'Images/icons/icon_heat.png', 'slutpit': 'Images/icons/icon_slutpit.png', 'heterovip': 'Images/icons/icon_heterovip.png', 'rainbow': 'Images/scenes/the_rainbow.png', 'gayvip': 'Images/scenes/the_gay_vip.png', 'transroom': 'Images/icons/icon_transroom.png', 'velvet': 'Images/icons/icon_velvet.png', 'dollhouse': 'Images/icons/icon_dollhouse.png', 'herturn': 'Images/icons/icon_herturn.png', 'trance': 'Images/icons/icon_trance.png', 'transvip': 'Images/icons/icon_transvip.png', 'penthouse': 'Images/scenes/the_penthouse.png' }; (function applyTableBg() { var at = State.variables.activeTable; var img = at && _TBG[at] ? _TBG[at] : null; document.body.style.backgroundImage = img ? 'linear-gradient(rgba(0,0,0,0.62),rgba(0,0,0,0.55),rgba(0,0,0,0.62)), url("' + img + '")' : ''; document.body.style.backgroundSize = 'cover'; document.body.style.backgroundPosition = 'center'; document.body.style.backgroundAttachment = 'fixed'; })(); /* ── Boutons rapides — barre fixe au bas de l'écran de jeu ── */ var canReplay = v.playerMoney >= (v.baseBet || v.currentBet); var replayBet = v.baseBet || v.currentBet; var quickBar = document.createElement('div'); quickBar.id = 'result-action-bar'; quickBar.style.cssText = [ 'position:fixed', 'left:240px', /* après le HUD fixe */ 'right:0', 'bottom:0', /* la save bar est en haut : rien a reserver en bas */ 'z-index:80', 'display:flex', 'gap:14px', 'align-items:center', 'justify-content:center', 'padding:14px 24px', 'background:rgba(6,4,15,0.92)', 'border-top:1px solid rgba(255,45,120,0.25)', 'backdrop-filter:blur(10px)', 'flex-wrap:wrap' ].join(';'); if (canReplay) { var btnReplay = document.createElement('a'); btnReplay.className = 'btn btn-primary result-qbtn'; btnReplay.style.cssText = 'font-size:1rem!important;padding:14px 28px!important;letter-spacing:0.08em!important;'; btnReplay.innerHTML = '🎰 Same bet — ' + formatMoney(replayBet); btnReplay.addEventListener('click', function() { v.currentBet = replayBet; Engine.play('Deal Cards'); }); quickBar.appendChild(btnReplay); } var btnBets = document.createElement('a'); btnBets.className = 'btn btn-secondary result-qbtn'; btnBets.style.cssText = 'font-size:1rem!important;padding:14px 28px!important;letter-spacing:0.08em!important;'; btnBets.innerHTML = '💸 Change bet'; btnBets.addEventListener('click', function() { Engine.play('Blackjack Table'); }); quickBar.appendChild(btnBets); var btnShop = document.createElement('a'); btnShop.className = 'btn btn-secondary result-qbtn result-qbtn-shop'; btnShop.style.cssText = 'font-size:1rem!important;padding:14px 28px!important;letter-spacing:0.08em!important;'; var _shopTable = window.TABLES.find(function(t) { return t.id === v.activeTable; }); btnShop.innerHTML = '🛍️ ' + (_shopTable ? _shopTable.name + ' Shop' : 'Card Shop'); btnShop.addEventListener('click', function() { v._shopFocusTable = v.activeTable; Engine.play('Card Shop'); }); quickBar.appendChild(btnShop); var btnCasino = document.createElement('a'); btnCasino.className = 'btn btn-stand result-qbtn'; btnCasino.style.cssText = 'font-size:1rem!important;padding:14px 28px!important;letter-spacing:0.08em!important;'; btnCasino.innerHTML = '🏠 Casino'; btnCasino.addEventListener('click', function() { Engine.play('Casino'); }); quickBar.appendChild(btnCasino); document.body.appendChild(quickBar); /* ══════════════════════════════════════════ DOUBLE OR NOTHING Propose de risquer le gain (argent + jetons) qui vient d'être crédité sur cette main, contre un tirage rouge/noir sur un jeu de 52 cartes complet (26 rouges / 26 noires → exactement 50/50). Usage unique par main : $lastHandWinMoney est remis à 0 après résolution pour empêcher tout re-crédit accidentel (rafraîchissement, double-clic...). ══════════════════════════════════════════ */ var SUITS_DON = [ { sym: '♠', color: 'black' }, { sym: '♣', color: 'black' }, { sym: '♥', color: 'red' }, { sym: '♦', color: 'red' } ]; var RANKS_DON = ['2','3','4','5','6','7','8','9','10','J','Q','K','A']; function renderDoubleOrNothing() { var zone = document.getElementById('dbl-nothing-zone'); if (!zone) return; if (!v.lastHandCanDouble || !v.lastHandWinMoney) { zone.innerHTML = ''; return; } var winMoney = v.lastHandWinMoney; var winTokens = v.lastHandTokens || 0; zone.innerHTML = '<div class="dbl-panel">' + '<div class="dbl-head">' + '<span class="dbl-title">💰 Double or Nothing</span>' + '<span class="dbl-sub">Risk this hand\'s payout — <strong>' + formatMoney(winMoney) + (winTokens > 0 ? ' + 🎫 ' + winTokens : '') + '</strong> — on a single card. Guess the color.</span>' + '</div>' + '<div class="dbl-body" id="dbl-body">' + '<button class="dbl-choice dbl-choice-red" data-color="red">' + '<span class="dbl-choice-suits">♥ ♦</span><span class="dbl-choice-label">Red</span>' + '</button>' + '<div class="dbl-vs">or</div>' + '<button class="dbl-choice dbl-choice-black" data-color="black">' + '<span class="dbl-choice-suits">♠ ♣</span><span class="dbl-choice-label">Black</span>' + '</button>' + '</div>' + '</div>'; zone.querySelectorAll('.dbl-choice').forEach(function(btn) { btn.addEventListener('click', function() { resolveDoubleOrNothing(this.getAttribute('data-color'), winMoney, winTokens); }); }); } function resolveDoubleOrNothing(choice, winMoney, winTokens) { var body = document.getElementById('dbl-body'); if (!body) return; /* Verrouillage immédiat : un seul tirage possible par main */ v.lastHandCanDouble = false; body.innerHTML = '<div class="rv-card rv-card-lg dbl-card dbl-card-shuffle" id="dbl-card">?</div>'; var cardEl = document.getElementById('dbl-card'); setTimeout(function() { var suit = SUITS_DON[Math.floor(Math.random() * SUITS_DON.length)]; var rank = RANKS_DON[Math.floor(Math.random() * RANKS_DON.length)]; var won = (suit.color === choice); cardEl.className = 'rv-card rv-card-lg dbl-card dbl-card-reveal' + (suit.color === 'red' ? ' rv-card-red' : ''); cardEl.innerHTML = rank + '<span>' + suit.sym + '</span>'; setTimeout(function() { if (won) { v.playerMoney += winMoney; if (winTokens > 0) { if (!v.tokens) v.tokens = {}; v.tokens[v.activeTable] = (v.tokens[v.activeTable] || 0) + winTokens; } } else { v.playerMoney = Math.max(0, v.playerMoney - winMoney); if (winTokens > 0 && v.tokens) { v.tokens[v.activeTable] = Math.max(0, (v.tokens[v.activeTable] || 0) - winTokens); } } /* Usage unique consommé : impossible de rejouer sur cette main */ v.lastHandWinMoney = 0; var panel = document.querySelector('#dbl-nothing-zone .dbl-panel'); if (panel) { panel.insertAdjacentHTML('beforeend', '<div class="dbl-outcome ' + (won ? 'dbl-outcome-win' : 'dbl-outcome-lose') + '">' + (won ? '✅ Doubled! ' + formatMoney(winMoney) + ' → <strong>' + formatMoney(winMoney * 2) + '</strong>' + (winTokens > 0 ? ' · 🎫 ' + winTokens + ' → <strong>' + (winTokens * 2) + '</strong>' : '') : '❌ Lost it — -' + formatMoney(winMoney) + (winTokens > 0 ? ' · 🎫 -' + winTokens : '')) + '</div>'); } /* Le HUD fixe (déjà affiché) doit refléter le nouveau solde */ var hudVals = document.querySelectorAll('#game-hud .hud-stat-value'); if (hudVals[0]) hudVals[0].textContent = formatMoney(v.playerMoney); if (hudVals[2]) hudVals[2].textContent = (v.tokens ? (v.tokens[v.activeTable] || 0) : 0); }, 650); }, 500); } renderDoubleOrNothing(); /* ── STREAK REWARDS ── */ (function() { var v = State.variables; var streak = (v.currentStreak && v.currentStreak[v.activeTable]) ? v.currentStreak[v.activeTable] : 0; var MILESTONES = [ { streak: 3, key: 's3', reward: function() { var tbl = v.activeTable || 'vice'; if (!v.tokens) v.tokens = {}; v.tokens[tbl] = (v.tokens[tbl] || 0) + 20; }, rewardLabel: '+20 tokens on this table', scarlettMsg: 'Three in a row. You\'re getting the hang of this. I added 20 tokens to your table — consider it a tip.', transMsg: 'Three in a row, gorgeous! I slipped 20 tokens onto your table. Don\'t spend them all at once.', tips: [ '🔥 Win 3 hands in a row → +20 tokens', '🔥🔥 Win 5 in a row → +$150 cash', '🔥🔥🔥 Win 6 in a row → 1 free pack', '👑 Win 8 in a row → New cards pack + $300' ] }, { streak: 5, key: 's5', reward: function() { v.playerMoney += 150; }, rewardLabel: '+$150 cash', scarlettMsg: 'Five straight wins. The table is yours tonight. Here\'s $150 — you\'ve earned it.', transMsg: 'Five wins straight. You\'re on fire tonight. Here\'s $150, you\'ve earned every cent.', tips: [ '🔥🔥 5-win streak → +$150 cash ✓', '🔥🔥🔥 Win 6 in a row → 1 free pack', '👑 Win 8 in a row → New cards pack + $300', '⚠️ One loss resets the streak — stay focused' ] }, { streak: 6, key: 's7', reward: function() { var pack = window.PACKS.find(function(p) { return p.table === (v.activeTable || 'vice') && !p.forcedRarity; }); if (pack) { var result = window.openPack(pack.id, true); if (result === true) v._streakFreePack = pack.id; } }, rewardLabel: '1 free pack', scarlettMsg: 'Six wins. That\'s not luck anymore — that\'s skill. I\'m impressed. Take a free pack, you deserve it.', transMsg: 'Six in a row? Okay, now you\'re just showing off. I love it. Have a free pack on me.', tips: [ '🔥🔥🔥 6-win streak → free pack ✓', '👑 Win 8 in a row → New cards pack + $300', '🎴 Your free pack has been added — go open it', '⚠️ One loss resets the streak' ] }, { streak: 8, key: 's10', reward: function() { v.playerMoney += 300; /* Pack avec cartes non possédées uniquement */ var tbl = v.activeTable || 'vice'; var pack = window.PACKS.find(function(p) { return p.table === tbl && !p.forcedRarity; }); if (pack) { var col = v.collection || {}; var pool = window.CARD_DB.filter(function(c) { return pack.categories.indexOf(c.category) !== -1 && col[c.id] === undefined; }); /* Fallback si tout est possédé */ if (!pool.length) pool = window.CARD_DB.filter(function(c) { return pack.categories.indexOf(c.category) !== -1; }); if (pool.length) { if (!v.tokens) v.tokens = {}; /* Déclencher l'ouverture de pack directement */ v.currentPackId = pack.id; v.currentPackXXL = false; v.pendingPack = []; for (var i = 0; i < 3; i++) { var shuffled = pool.slice().sort(function() { return Math.random() - 0.5; }); var card = shuffled[0]; if (card) { v.pendingPack.push(card.id); if (v.collection[card.id] === undefined) v.collection[card.id] = 0; v.collection[card.id]++; /* Retirer du pool pour éviter doublons dans ce pack */ pool = pool.filter(function(c) { return c.id !== card.id; }); if (!pool.length) pool = window.CARD_DB.filter(function(c) { return pack.categories.indexOf(c.category) !== -1; }); } } v._streakNewCardsPack = true; } } }, rewardLabel: 'New cards pack + $300', scarlettMsg: 'Eight. Wins. In. A. Row. I\'ve never seen anything like it. Take the $300. And here\'s a pack — I made sure every card in it is something you don\'t have yet.', transMsg: 'Eight wins in a row. I\'m speechless, and that never happens. Take the $300, and this pack. Every single card in it is one you don\'t have yet.', tips: [ '👑 8-win streak → New cards only pack + $300 ✓', '💰 $300 added to your balance', '🎴 Go open your pack — all new cards guaranteed', '🔥 The streak resets — can you do it again?' ] } ]; /* Trouver le milestone atteint */ var milestone = null; for (var i = MILESTONES.length - 1; i >= 0; i--) { if (streak === MILESTONES[i].streak) { milestone = MILESTONES[i]; break; } } if (!milestone) return; /* Appliquer la récompense */ milestone.reward(); /* Bulle d'hotesse — premiere fois seulement (par monde). Toast discret ensuite */ var _skey = 'streak_' + milestone.key + (v.activeBranch === 'trans' ? '_trans' : ''); var isFirst = v.activeBranch === 'hetero' ? !v.streakMilestoneSeen[milestone.key] : !(v.visitedPages && v.visitedPages[_skey]); if (isFirst && v.activeBranch === 'hetero') v.streakMilestoneSeen[milestone.key] = true; var delay = (v.lastHandResult === 'blackjack') ? 2500 : 700; if (isFirst) { setTimeout(function() { window.showFirstVisitTip('streak_' + milestone.key, { icon: '🔥', title: 'Streak Bonus — ' + milestone.rewardLabel, body: milestone.rewardLabel, bodyScarlett: milestone.scarlettMsg, bodyTrans: milestone.transMsg, tips: milestone.tips, onClose: (v._streakFreePack || v._streakNewCardsPack) ? function() { v._streakFreePack = null; v._streakNewCardsPack = false; Engine.play('Open Pack'); } : null, closeBtnLabel: (v._streakFreePack || v._streakNewCardsPack) ? '🎴 Open Pack' : null }); }, delay); } else { /* Toast discret */ setTimeout(function() { var icons = { s3:'🔥', s5:'🔥🔥', s7:'🔥🔥🔥', s10:'👑' }; var toast = document.createElement('div'); toast.className = 'streak-toast'; toast.innerHTML = '<span class="streak-toast-icon">' + (icons[milestone.key] || '🔥') + '</span>' + '<span class="streak-toast-text">' + streak + '-win streak! <strong>' + milestone.rewardLabel + '</strong></span>' + ((v._streakFreePack || v._streakNewCardsPack) ? '<button class="streak-toast-pack">🎴 Open Pack</button>' : ''); document.body.appendChild(toast); if (v._streakFreePack || v._streakNewCardsPack) { toast.querySelector('.streak-toast-pack').addEventListener('click', function() { toast.remove(); v._streakFreePack = null; v._streakNewCardsPack = false; Engine.play('Open Pack'); }); } /* Apparition */ setTimeout(function() { toast.classList.add('streak-toast-visible'); }, 50); /* Disparition auto après 4s */ setTimeout(function() { toast.classList.remove('streak-toast-visible'); setTimeout(function() { if (toast.parentNode) toast.remove(); }, 400); }, 4000); }, delay); } })(); if (v.lastResultBlackjack) { /* Trouver le pack standard de la table active */ var freePack = PACKS.find(function(p) { return p.table === v.activeTable && !p.forcedRarity; }); /* Lancer l'animation après un court délai */ setTimeout(function() { triggerBlackjackCelebration(freePack); }, 400); } /* ── LOT DE CONSOLATION (serie de pertes) ── */ if (v.consolationPending) { setTimeout(function() { window.showLossConsolation(); }, 900); } /* ── PREMIER GAIN : tip Scarlett ── */ /* Une premiere victoire par hotesse (la bulle elle-meme ne s'affiche qu'une fois) */ var _fwKey = v.activeBranch === 'trans' ? 'first_win_trans' : 'first_win'; if (window.currentHost() && !(v.visitedPages && v.visitedPages[_fwKey]) && (v.lastHandResult === 'win' || v.lastHandResult === 'blackjack')) { v.firstWinSeen = true; var tokensJustEarned = v.lastHandTokens || 0; setTimeout(function() { window.showFirstVisitTip('first_win', { icon: '🎰', title: 'First Win', body: 'You just won your first hand and earned tokens.', bodyScarlett: 'Not bad for a start. You just earned ' + tokensJustEarned + ' tokens for this table — don\'t let them sit there. Head to the Card Shop and see what I\'ve got waiting for you. Or... if you\'re feeling bold, there\'s a way to double what you just won sitting right there on the table. Your call.', bodyTrans: 'Look at you, first win of the night! You just earned ' + tokensJustEarned + ' tokens for this table. Spend them in the Card Shop... or, if you\'re feeling brave, there\'s a way to double that win right there on the table.', tips: [ '🎫 Tokens are earned on every winning hand', '🛍️ Spend them in the Card Shop to open packs', '💰 Feeling lucky? Double or Nothing risks this win on one card', '🌟 Common to Legendary — rarity is rolled on every card' ] }); }, 600); } })(); function triggerBlackjackCelebration(freePack) { /* Supprimer ancienne overlay si présente */ var oldOverlay = document.getElementById('bj-celebration'); if (oldOverlay) oldOverlay.remove(); var packName = freePack ? freePack.emoji + ' ' + freePack.name : 'a free pack'; var overlay = document.createElement('div'); overlay.id = 'bj-celebration'; overlay.innerHTML = '<div class="bj-backdrop"></div>' + '<div class="bj-content">' + '<div class="bj-stars" id="bj-stars"></div>' + '<div class="bj-cards">' + '<span class="bj-mini-card bj-mini-red">A<small>♥</small></span>' + '<span class="bj-mini-card">K<small>♠</small></span>' + '</div>' + '<div class="bj-eyebrow">Twenty-one</div>' + '<div class="bj-title">BLACKJACK</div>' + '<div class="bj-rule"></div>' + '<div class="bj-subtitle">A perfect hand. The house owes you one.</div>' + '<div class="bj-pack-reward">' + '<div class="bj-pack-label">Your reward</div>' + '<div class="bj-pack-row">' + '<span class="bj-pack-icon">' + (freePack ? freePack.emoji : '🎴') + '</span>' + '<span class="bj-pack-name">' + packName + '</span>' + '</div>' + '</div>' + '<button class="btn bj-btn-open" id="bj-btn-open">Open the pack</button>' + '</div>'; document.body.appendChild(overlay); /* Générer étoiles/particules */ var stars = document.getElementById('bj-stars'); for (var i = 0; i < 14; i++) { var star = document.createElement('div'); star.className = 'bj-star'; star.style.cssText = 'left:' + (Math.random() * 100) + '%;' + 'animation-delay:' + (Math.random() * 0.8) + 's;' + 'animation-duration:' + (1.2 + Math.random() * 1.2) + 's;' + 'font-size:' + (0.4 + Math.random() * 0.5) + 'rem;'; star.textContent = '✦'; stars.appendChild(star); } /* Ouvrir le pack maintenant */ document.getElementById('bj-btn-open').addEventListener('click', function() { overlay.remove(); if (freePack) { var result = openPack(freePack.id, true); /* true = gratuit */ if (result === true || result === 'free') { Engine.play('Open Pack'); } } }); } <</script>> <</done>>
<div class="htp-screen" id="htp-screen"> <div class="htp-header"> <p class="htp-pretitle">◈ Welcome to ◈</p> <h1 class="game-title htp-title">THE <span>21</span> DESIRES</h1> <p class="htp-subtitle">Here's how it works — it only takes a minute.</p> </div> <div class="htp-steps"> <!-- ÉTAPE 1 : BLACKJACK --> <div class="htp-step htp-step-1"> <div class="htp-step-icon-wrap"> <div class="htp-step-num">01</div> <div class="htp-step-icon">🃏</div> </div> <div class="htp-step-body"> <h2 class="htp-step-title">Play Blackjack</h2> <p class="htp-step-desc">Choose your table, place your bet and beat the dealer. Standard rules — hit, stand, double down or split. Get closer to 21 than the house without going over.</p> <p class="htp-step-hint">💡 New to Blackjack? Full rules are available anytime in the <strong>Rules</strong> section of the sidebar.</p> <div class="htp-demo-cards"> <div class="htp-card htp-card-red"><span class="htp-c-tl">A<br>♥</span><span class="htp-c-mid">♥</span><span class="htp-c-br">A<br>♥</span></div> <div class="htp-card htp-card-black"><span class="htp-c-tl">K<br>♠</span><span class="htp-c-mid">♠</span><span class="htp-c-br">K<br>♠</span></div> <div class="htp-card-result">= <span class="htp-bj-label">BLACKJACK ⭐</span></div> </div> </div> </div> <!-- ÉTAPE 2 : TOKENS --> <div class="htp-step htp-step-2"> <div class="htp-step-icon-wrap"> <div class="htp-step-num">02</div> <div class="htp-step-icon">🎫</div> </div> <div class="htp-step-body"> <h2 class="htp-step-title">Earn Tokens</h2> <p class="htp-step-desc">Every winning hand earns you tokens — the bigger your bet, the more you pocket. Tokens are tied to each table and spent in the Card Shop.</p> <div class="htp-token-demo"> <div class="htp-token-row"><span class="htp-bet">$50 bet</span><span class="htp-arrow">→</span><span class="htp-tok">+10 🎫</span></div> <div class="htp-token-row"><span class="htp-bet">$100 bet</span><span class="htp-arrow">→</span><span class="htp-tok">+20 🎫</span></div> <div class="htp-token-row htp-tok-gold"><span class="htp-bet">$250 bet</span><span class="htp-arrow">→</span><span class="htp-tok">+50 🎫</span></div> </div> </div> </div> <!-- ÉTAPE 3 : CARTES ADULTES --> <div class="htp-step htp-step-3"> <div class="htp-step-icon-wrap"> <div class="htp-step-num">03</div> <div class="htp-step-icon">💎</div> </div> <div class="htp-step-body"> <h2 class="htp-step-title">Collect Adult Cards</h2> <p class="htp-step-desc">This is where it gets hot. Spend your tokens in the Card Shop to open packs and unlock <strong>explicit adult content</strong> — animated cards across dozens of categories. Each pack reveals 3 cards, ranging from common to legendary rarity. The more tables you unlock, the more content becomes available.</p> <p class="htp-step-desc" style="margin-top:2px;">Below — a preview of what's waiting for you in the packs:</p> <!-- Vraies cartes GIF depuis la base --> <div class="htp-pack-demo"> <div class="htp-pack-card" id="htp-pc-1"> <div class="htp-pc-inner htp-pc-back"> <div class="htp-pc-back-pattern"><span>21</span></div> </div> </div> <div class="htp-pack-card" id="htp-pc-2"> <div class="htp-pc-inner htp-pc-back"> <div class="htp-pc-back-pattern"><span>21</span></div> </div> </div> <div class="htp-pack-card" id="htp-pc-3"> <div class="htp-pc-inner htp-pc-back"> <div class="htp-pc-back-pattern"><span>21</span></div> </div> </div> </div> <button class="htp-reveal-btn" id="htp-reveal-btn">🎴 Reveal pack preview</button> <div class="htp-rarity-legend"> <span class="htp-rar htp-rar-common">⚪ Common</span> <span class="htp-rar htp-rar-rare">🔵 Rare</span> <span class="htp-rar htp-rar-epic">🟣 Epic</span> <span class="htp-rar htp-rar-legendary">🌟 Legendary</span> </div> </div> </div> <!-- BONUS : Lucky Wheel --> <div class="htp-step htp-step-bonus"> <div class="htp-step-icon-wrap"> <div class="htp-step-num htp-bonus-num">✦</div> <div class="htp-step-icon">🎡</div> </div> <div class="htp-step-body"> <h2 class="htp-step-title">Lucky Wheel <span class="htp-bonus-tag">Bonus</span></h2> <p class="htp-step-desc">Every 20 hands played, the Lucky Wheel unlocks. Spin it to win cash, tokens or a random card — for free. A little extra luck never hurt anyone.</p> </div> </div> </div> <div class="htp-cta"> <<link "✦ I'm ready — Choose my World" "World Select">><</link>> </div> </div> <<done>> <<script>> (function() { /* Style le bouton CTA */ var cta = document.querySelector('.htp-cta a'); if (cta) cta.classList.add('btn', 'btn-primary', 'htp-cta-btn'); /* ── Demo pack : 3 vraies cartes fixes (common / rare / legendary) ── */ var DEMO_PACK = [ { file: 'Images/cards/girl_strip/girl_strip_1.webp', name: 'Girl Strip #1', rarity: 'common', label: 'Common', color: '#aaaaaa', icon: '⚪' }, { file: 'Images/cards/girl_strip/girl_strip_4.webp', name: 'Girl Strip #4', rarity: 'rare', label: 'Rare', color: '#60a5fa', icon: '🔵' }, { file: 'Images/cards/girl_strip/girl_strip_8.webp', name: 'Girl Strip #8', rarity: 'legendary', label: 'Legendary', color: '#ffd700', icon: '🌟' } ]; var revealed = false; var revealBtn = document.getElementById('htp-reveal-btn'); function revealCard(id, card, delay) { setTimeout(function() { var el = document.getElementById(id); if (!el) return; el.innerHTML = '<div class="htp-pc-inner htp-pc-front" style="border-color:' + card.color + ';box-shadow:0 0 20px ' + card.color + '55;">' + '<img class="htp-pc-gif" src="' + card.file + '" alt="' + card.name + '">' + '<div class="htp-pc-footer">' + '<span class="htp-pc-rarity-icon">' + card.icon + '</span>' + '<span class="htp-pc-label" style="color:' + card.color + '">' + card.label + '</span>' + '</div>' + '</div>'; el.classList.add('htp-pc-revealed'); }, delay); } if (revealBtn) { revealBtn.addEventListener('click', function() { if (revealed) return; /* une seule fois */ var ids = ['htp-pc-1','htp-pc-2','htp-pc-3']; DEMO_PACK.forEach(function(card, i) { revealCard(ids[i], card, i * 600); }); revealed = true; revealBtn.disabled = true; revealBtn.style.opacity = '0.4'; revealBtn.style.cursor = 'default'; }); } })(); <</script>> <</done>> <style> /* Garde-fou : voir commentaire équivalent dans le passage Warning. Chaque conteneur flex de cette page reçoit ses enfants sur des lignes séparées dans le source — sans ce garde-fou, chacun ajoute un espacement fantôme (ou pire, casse la mise en page dans le cas d'une grille). */ .htp-screen br, .htp-header br, .htp-steps br, .htp-step br, .htp-step-icon-wrap br, .htp-step-body br, .htp-step-title br, .htp-demo-cards br, .htp-card br, .htp-card-result br, .htp-token-demo br, .htp-token-row br, .htp-pack-demo br, .htp-pc-inner br, .htp-pc-footer br, .htp-rarity-legend br, .htp-cta br { display: none; } /* ══════════════════════════════════════════ HOW TO PLAY ══════════════════════════════════════════ */ .htp-screen { min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 26px 24px 34px; gap: 20px; max-width: 880px; margin: 0 auto; } .htp-header { text-align: center; display: flex; flex-direction: column; gap: 4px; } .htp-pretitle { font-family: 'Share Tech Mono', monospace; font-size: 0.88rem; letter-spacing: 0.4em; color: rgba(255,45,120,0.7); text-transform: uppercase; } .htp-title { font-size: clamp(1.8rem, 5vw, 3rem) !important; } .htp-subtitle { font-family: 'Rajdhani', sans-serif; font-size: 1.05rem; color: rgba(210,200,235,0.75); letter-spacing: 0.08em; margin-top: 2px; } /* ── Steps ── */ .htp-steps { display: flex; flex-direction: column; gap: 12px; width: 100%; } .htp-step { display: flex; gap: 16px; align-items: flex-start; background: rgba(14,10,30,0.75); border: 1px solid rgba(255,255,255,0.06); border-radius: 18px; padding: 18px 22px; backdrop-filter: blur(4px); position: relative; overflow: hidden; } .htp-step::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px; border-radius: 18px 18px 0 0; } .htp-step-1::before { background: linear-gradient(90deg, transparent, var(--neon-cyan), transparent); } .htp-step-2::before { background: linear-gradient(90deg, transparent, var(--gold), transparent); } .htp-step-3::before { background: linear-gradient(90deg, transparent, var(--neon-purple), transparent); } .htp-step-bonus::before { background: linear-gradient(90deg, transparent, var(--neon-pink), transparent); } .htp-step-icon-wrap { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; flex-shrink: 0; width: 52px; height: 52px; border-radius: 12px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.07); /* Filet de sécurité : certaines polices système donnent aux emoji (🃏, 🎡...) une boîte de ligne plus haute que leur taille visuelle nominale, ce qui les faisait dépasser du cadre arrondi. */ overflow: hidden; } .htp-step-num { font-family: 'Orbitron', monospace; font-size: 0.58rem; font-weight: 900; letter-spacing: 0.12em; line-height: 1; color: rgba(255,45,120,0.8); } .htp-bonus-num { color: rgba(255,215,0,0.6); } .htp-step-icon { font-size: 1.6rem; line-height: 1; } .htp-step-body { display: flex; flex-direction: column; gap: 9px; flex: 1; } .htp-step-title { font-family: 'Orbitron', monospace; font-size: 1.1rem; font-weight: 900; color: var(--text-primary); letter-spacing: 0.08em; display: flex; align-items: center; gap: 10px; margin: 0; } .htp-bonus-tag { font-size: 0.62rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase; background: rgba(255,215,0,0.12); border: 1px solid rgba(255,215,0,0.3); color: var(--gold); padding: 3px 8px; border-radius: 20px; } .htp-step-desc { font-family: 'Rajdhani', sans-serif; font-size: 1rem; font-weight: 500; color: rgba(218, 210, 240, 0.9); line-height: 1.45; margin: 0; } .htp-step-desc strong { color: #fff; font-weight: 700; } .htp-step-hint { font-family: 'Rajdhani', sans-serif; font-size: 0.92rem; font-weight: 500; color: rgba(0,245,255,0.65); background: rgba(0,245,255,0.05); border: 1px solid rgba(0,245,255,0.12); border-radius: 8px; padding: 7px 12px; margin: 0; line-height: 1.4; } .htp-step-hint strong { color: rgba(0,245,255,0.9); font-weight: 700; } /* ── Demo blackjack cards ── */ .htp-demo-cards { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } .htp-card { width: 64px; height: 92px; border-radius: 8px; background: white; border: 2px solid rgba(255,255,255,0.8); display: flex; align-items: center; justify-content: center; position: relative; box-shadow: 0 4px 14px rgba(0,0,0,0.5); animation: dealCard 0.4s ease-out; } .htp-c-tl { position: absolute; top: 4px; left: 5px; font-family: 'Share Tech Mono', monospace; font-size: 0.78rem; font-weight: bold; line-height: 1.2; text-align: center; } .htp-c-br { position: absolute; bottom: 4px; right: 5px; font-family: 'Share Tech Mono', monospace; font-size: 0.78rem; font-weight: bold; line-height: 1.2; text-align: center; transform: rotate(180deg); } .htp-c-mid { font-size: 1.6rem; line-height: 1; } .htp-card-red .htp-c-tl, .htp-card-red .htp-c-br, .htp-card-red .htp-c-mid { color: #cc0022; } .htp-card-black .htp-c-tl, .htp-card-black .htp-c-br, .htp-card-black .htp-c-mid { color: #111; } .htp-card-result { font-family: 'Rajdhani', sans-serif; font-size: 1.05rem; font-weight: 700; color: rgba(255,255,255,0.55); display: flex; align-items: center; gap: 6px; } .htp-bj-label { font-family: 'Orbitron', monospace; font-size: 0.82rem; font-weight: 900; color: var(--gold); text-shadow: 0 0 12px rgba(255,215,0,0.5); letter-spacing: 0.1em; } /* ── Token demo ── */ .htp-token-demo { display: flex; flex-wrap: wrap; gap: 8px; } .htp-token-row { display: flex; align-items: center; gap: 9px; font-family: 'Share Tech Mono', monospace; font-size: 0.9rem; padding: 7px 12px; background: rgba(0,245,255,0.04); border: 1px solid rgba(0,245,255,0.1); border-radius: 8px; } .htp-bet { color: rgba(255,255,255,0.72); } .htp-arrow { color: rgba(255,255,255,0.22); } .htp-tok { color: var(--neon-cyan); font-weight: bold; } .htp-tok-gold { background: rgba(255,215,0,0.05); border-color: rgba(255,215,0,0.18); } .htp-tok-gold .htp-tok { color: var(--gold); } .htp-tok-gold .htp-bet { color: var(--gold); opacity: 0.85; } /* ── Pack demo avec vrais GIFs ── */ .htp-pack-demo { display: flex; gap: 14px; flex-wrap: wrap; } .htp-pack-card { width: 110px; height: 156px; perspective: 700px; cursor: default; flex-shrink: 0; } .htp-pc-inner { width: 100%; height: 100%; border-radius: 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; border: 2px solid; overflow: hidden; } .htp-pc-back { background: repeating-linear-gradient(45deg, rgba(255,45,120,0.12) 0 2px, transparent 2px 12px), repeating-linear-gradient(-45deg, rgba(191,0,255,0.12) 0 2px, transparent 2px 12px), radial-gradient(ellipse at 50% 0%, #2a1246, #150a2b 60%, #0b0518 100%); border-color: rgba(255,45,120,0.35); box-shadow: inset 0 0 30px rgba(0,0,0,0.6), 0 0 14px rgba(191,0,255,0.2); } /* Même monogramme que le dos des cartes de pack, en réduction */ .htp-pc-back-pattern span { font-family: 'Orbitron', monospace; font-weight: 900; font-size: 2rem; letter-spacing: 0.04em; background: linear-gradient(180deg, #ff8ab5, #ff2d78 45%, #bf00ff); -webkit-background-clip: text; background-clip: text; color: transparent; filter: drop-shadow(0 0 12px rgba(255,45,120,0.45)); } .htp-pc-front { background: #0a0616; justify-content: flex-end; gap: 0; padding: 0; } .htp-pc-gif { width: 100%; flex: 1; object-fit: cover; display: block; min-height: 0; } .htp-pc-footer { width: 100%; display: flex; align-items: center; justify-content: center; gap: 5px; padding: 5px 6px; background: rgba(0,0,0,0.65); flex-shrink: 0; } .htp-pc-rarity-icon { font-size: 0.9rem; line-height: 1; } .htp-pc-label { font-family: 'Orbitron', monospace; font-size: 0.58rem; font-weight: 900; letter-spacing: 0.1em; text-transform: uppercase; } .htp-pack-card.htp-pc-revealed .htp-pc-inner { animation: pcReveal 0.5s cubic-bezier(0.34, 1.2, 0.64, 1); } @keyframes pcReveal { 0% { transform: rotateY(90deg) scale(0.85); opacity: 0; } 100% { transform: rotateY(0deg) scale(1); opacity: 1; } } .htp-reveal-btn { font-family: 'Rajdhani', sans-serif; font-size: 1rem; font-weight: 700; letter-spacing: 0.1em; padding: 11px 22px; border-radius: 10px; background: rgba(191,0,255,0.1); border: 1px solid rgba(191,0,255,0.3); color: #d966ff; cursor: pointer; transition: all 0.2s; align-self: flex-start; } .htp-reveal-btn:hover:not(:disabled) { background: rgba(191,0,255,0.2); border-color: var(--neon-purple); box-shadow: 0 0 16px rgba(191,0,255,0.25); } .htp-rarity-legend { display: flex; gap: 10px; flex-wrap: wrap; } .htp-rar { font-family: 'Share Tech Mono', monospace; font-size: 0.78rem; letter-spacing: 0.08em; padding: 4px 11px; border-radius: 20px; } .htp-rar-common { background: rgba(170,170,170,0.08); color: #bbb; border: 1px solid rgba(170,170,170,0.2); } .htp-rar-rare { background: rgba(96,165,250,0.08); color: #60a5fa; border: 1px solid rgba(96,165,250,0.25); } .htp-rar-epic { background: rgba(192,132,252,0.08); color: #c084fc; border: 1px solid rgba(192,132,252,0.25); } .htp-rar-legendary{ background: rgba(255,215,0,0.08); color: var(--gold); border: 1px solid rgba(255,215,0,0.3); } /* ── CTA ── */ .htp-cta { display: flex; justify-content: center; margin-top: 8px; } .htp-cta-btn { padding: 18px 52px !important; font-size: 1.12rem !important; letter-spacing: 0.14em !important; } </style>
<div class="intro-screen"> <div class="intro-bg"> <img src="Images/scenes/entry.png" class="intro-img" alt="The 21 Desires"> <div class="intro-overlay"></div> </div> <div class="intro-content"> <div class="intro-top"> <p class="intro-pretitle">◈ Step inside ◈</p> <h1 class="game-title intro-title">THE <span>21</span> DESIRES</h1> <p class="intro-subtitle">The cards never lie. But they always tempt.<br>Play your hand. Collect your fantasies.</p> </div> <div class="intro-bottom"> <<link "▶ Enter the Casino" "How To Play">><</link>> </div> </div> </div> <<done>> <<script>> (function() { const btn = document.querySelector('.intro-bottom a'); if (btn) btn.classList.add('btn', 'btn-intro-enter'); })(); <</script>> <</done>> <style> .intro-screen { position: fixed; inset: 0; width: 100vw; height: 100vh; overflow: hidden; display: flex; align-items: center; justify-content: center; z-index: 100; } .intro-bg { position: absolute; inset: 0; } .intro-img { width: 100%; height: 100%; object-fit: cover; object-position: center top; display: block; } .intro-overlay { position: absolute; inset: 0; background: /* Halo lumineux centré bas — fait ressortir les cartes sur la table */ radial-gradient(ellipse 60% 35% at 50% 85%, rgba(6,4,15,0.0) 0%, rgba(6,4,15,0.15) 40%, rgba(6,4,15,0.6) 100% ), /* Assombrissement des bords gauche/droit */ radial-gradient(ellipse 100% 100% at 50% 50%, transparent 40%, rgba(6,4,15,0.5) 100% ), /* Gradient vertical général — top sombre, milieu clair, bas modéré */ linear-gradient(to bottom, rgba(6,4,15,0.80) 0%, rgba(6,4,15,0.25) 30%, rgba(6,4,15,0.20) 55%, rgba(6,4,15,0.55) 75%, rgba(6,4,15,0.85) 100% ); } .intro-content { position: relative; z-index: 10; width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: space-between; padding: 48px 24px 60px; text-align: center; } .intro-top { display: flex; flex-direction: column; align-items: center; gap: 12px; animation: introFadeDown 1.2s ease-out; } .intro-pretitle { font-family: 'Share Tech Mono', monospace; font-size: 0.85rem; letter-spacing: 0.5em; color: rgba(255,45,120,0.8); text-transform: uppercase; text-shadow: 0 0 10px rgba(255,45,120,0.5); } .intro-title { font-size: clamp(2.5rem, 8vw, 5rem) !important; text-shadow: 0 0 20px var(--neon-pink), 0 0 50px rgba(255,45,120,0.4), 0 0 100px rgba(255,45,120,0.15) !important; } .intro-subtitle { font-family: 'Rajdhani', sans-serif; font-size: clamp(1rem, 2.5vw, 1.3rem); font-weight: 600; color: rgba(240,230,255,0.95); line-height: 1.7; letter-spacing: 0.05em; text-shadow: 0 2px 4px rgba(0,0,0,1), 0 0 20px rgba(0,0,0,0.9); margin-top: 8px; } .intro-bottom { animation: introFadeUp 1.4s ease-out; } .btn-intro-enter { padding: 18px 52px !important; font-size: 1.15rem !important; letter-spacing: 0.15em !important; background: linear-gradient(135deg, #c4005e, #ff2d78) !important; border: 1px solid var(--neon-pink) !important; color: white !important; box-shadow: 0 0 30px rgba(255,45,120,0.5), 0 0 60px rgba(255,45,120,0.2), inset 0 1px 0 rgba(255,255,255,0.15) !important; text-shadow: none !important; border-radius: 6px; transition: all 0.3s !important; display: inline-block; } .btn-intro-enter:hover { background: linear-gradient(135deg, #ff2d78, #ff6ea3) !important; box-shadow: 0 0 50px rgba(255,45,120,0.7), 0 0 100px rgba(255,45,120,0.3) !important; transform: translateY(-3px) scale(1.03); color: white !important; text-shadow: none !important; } @keyframes introFadeDown { from { opacity: 0; transform: translateY(-30px); } to { opacity: 1; transform: translateY(0); } } @keyframes introFadeUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } </style>
<div style="min-height:100vh; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; padding:40px 20px;"> <h1 class="game-title" style="margin-bottom:16px;">THE <span>21</span> DESIRES</h1> <p style="font-family:'Share Tech Mono',monospace; font-size:0.9rem; color:var(--text-muted); letter-spacing:0.2em; margin-bottom:40px;">ACCESS DENIED</p> <div style="font-size:4rem; margin-bottom:24px; filter:drop-shadow(0 0 15px rgba(255,51,102,0.5));">🚫</div> <p style="color:var(--text-muted); font-size:1rem; line-height:1.7; max-width:400px;">You have chosen to leave.<br>Thank you for your honesty.<br><br> <span style="color:var(--text-primary);">Come back when you're ready.</span></p> <div style="margin-top:36px;"> <<link "← Go Back" "Warning">><</link>> </div> </div> <<done>> <<script>> (function() { const link = document.querySelector('#passages a'); if (link) { link.style.fontFamily = "'Rajdhani', sans-serif"; link.style.fontSize = "0.95rem"; link.style.letterSpacing = "0.1em"; } })(); <</script>> <</done>>
<div class="casino-main coll-main"> <div class="coll-masthead"> <div class="coll-mast-left"> <div id="coll-brand-bar"></div> <div class="coll-title">My Collection</div> <div class="coll-sub">Every card you've unlocked — and everything still waiting for you.</div> </div> <div class="coll-mast-right"> <div class="coll-prog-head"> <span class="coll-prog-label">Branch completion</span> <span class="coll-prog-pct" id="coll-pct">0%</span> </div> <div class="coll-prog-count" id="coll-count">0<em> / 0</em></div> <div class="coll-prog-bar"><div class="coll-prog-fill" id="coll-fill"></div></div> <div class="coll-prog-meta"> <span class="cpm-item cpm-dupes"><b id="coll-dupes">0</b> duplicates</span> </div> </div> </div> <div class="coll-rarity-strip" id="coll-rarity-strip"></div> <div class="coll-toolbar"> <div class="coll-tb-row"> <div class="coll-search-wrap"> <span class="coll-search-icon">🔍</span> <input type="text" id="coll-search" class="coll-search" placeholder="Search a card…" autocomplete="off"> </div> <div class="coll-seg" id="coll-owned-seg"> <button class="coll-seg-btn active" data-owned="all">All</button> <button class="coll-seg-btn" data-owned="owned">Owned</button> <button class="coll-seg-btn" data-owned="missing">Missing</button> </div> <select class="coll-dropdown" id="coll-cat-select"> <option value="all">📂 All Categories</option> </select> <select class="coll-dropdown" id="coll-table-select"> <option value="all">🎰 All Tables</option> </select> </div> <div class="coll-tb-row coll-tb-row2"> <div class="coll-filters" id="coll-filters-rarity"> <button class="coll-filter active" data-rarity="all">All</button> <button class="coll-filter" data-rarity="legendary">🌟 Leg.</button> <button class="coll-filter" data-rarity="epic">🟣 Epic</button> <button class="coll-filter" data-rarity="rare">🔵 Rare</button> <button class="coll-filter" data-rarity="common">⚪ Common</button> </div> <div class="coll-sort-bar" id="coll-sort-bar"> <span class="coll-sort-label">Sort</span> <button class="coll-sort active" data-sort="chronological">📋 Category</button> <button class="coll-sort" data-sort="rarity">🌟 Rarity</button> <button class="coll-sort" data-sort="alpha">🔤 A → Z</button> </div> <span class="coll-results" id="coll-results"></span> <button class="coll-reset" id="coll-reset">✕ Reset filters</button> </div> </div> <div id="coll-grid" class="coll-grid"></div> </div> <<done>> <<script>> (function() { /* ══════════════════════════════════════════ REGLAGE : vignettes figees dans la grille ────────────────────────────────────────── true = les cartes de la grille sont figees sur leur 1re image (canvas). Aucune animation ne tourne -> beaucoup moins de CPU/batterie quand la collection est grande. L'animation reste normale au clic (lightbox). false = comportement d'origine : toutes les cartes s'animent en permanence dans la grille. ══════════════════════════════════════════ */ var FREEZE_THUMBS = true; var v = State.variables; var col = v.collection; var grid = document.getElementById('coll-grid'); var fRarity = document.getElementById('coll-filters-rarity'); var catSel = document.getElementById('coll-cat-select'); var tableSel = document.getElementById('coll-table-select'); var sortBar = document.getElementById('coll-sort-bar'); var searchIn = document.getElementById('coll-search'); var ownedSeg = document.getElementById('coll-owned-seg'); var resetBtn = document.getElementById('coll-reset'); var resultsEl = document.getElementById('coll-results'); if (!grid) return; var activeRarity = 'all'; var activeCat = 'all'; var activeTable = 'all'; var activeSort = 'chronological'; var activeOwned = 'all'; var searchTerm = ''; var rarityOrder = { legendary:0, epic:1, rare:2, common:3 }; /* ── Perimetre = branche active (hetero / gay / trans) ── */ var BRANCH = v.activeBranch || 'hetero'; var collBrand = document.getElementById('coll-brand-bar'); if (collBrand && window.branchBanner) collBrand.innerHTML = window.branchBanner('My Collection'); /* Categories de la branche, triees alphabetiquement pour le menu deroulant */ var BRANCH_CATS = window.CATEGORIES .filter(function(c) { return c.branch === BRANCH; }) .slice() .sort(function(a, b) { return a.label.localeCompare(b.label); }); /* Ordre de progression des tables, par branche */ var TABLE_ORDERS = { hetero: ['vice','boudoir','heat','slutpit','heterovip'], gay: ['rainbow','gayvip'], trans: ['transroom','transvip'] }; var tableOrder = TABLE_ORDERS[BRANCH] || TABLE_ORDERS.hetero; var fallbackTable = tableOrder[tableOrder.length - 1]; /* Mapping catégorie → table (basé sur les packs de la branche) */ var catToTable = {}; window.PACKS.filter(function(p) { return p.branch === BRANCH; }).forEach(function(pack) { /* On prend la table la plus "tôt" dans la progression */ pack.categories.forEach(function(cat) { if (!catToTable[cat]) catToTable[cat] = pack.table; else { var cur = tableOrder.indexOf(catToTable[cat]); var nw = tableOrder.indexOf(pack.table); if (nw < cur) catToTable[cat] = pack.table; } }); }); /* Populate dropdown catégories — on masque celles encore sans carte, sinon les categories declarees a l'avance polluent la liste. */ BRANCH_CATS.filter(function(cat) { return window.CARD_DB.some(function(c) { return c.category === cat.tag; }); }).forEach(function(cat) { var opt = document.createElement('option'); opt.value = cat.tag; opt.textContent = cat.label; catSel.appendChild(opt); }); /* Table labels : lus depuis TABLES pour rester synchro avec les renommages */ var tableLabels = {}; (window.TABLES || []).forEach(function(t) { tableLabels[t.id] = t.name; }); /* Populate dropdown tables (celles de la branche, dans l'ordre de progression) */ if (tableSel) { tableOrder.forEach(function(tid) { var tObj = (window.TABLES || []).find(function(t) { return t.id === tid; }); if (!tObj) return; var opt = document.createElement('option'); opt.value = tid; opt.textContent = (tObj.emoji ? tObj.emoji + ' ' : '') + tObj.name; tableSel.appendChild(opt); }); } function getCardTable(card) { return catToTable[card.category] || fallbackTable; } /* ── Statistiques d'en-tête (périmètre = branche affichée) ── */ var branchCatTags = BRANCH_CATS.map(function(c) { return c.tag; }); var branchCards = window.CARD_DB.filter(function(c) { return branchCatTags.indexOf(c.category) !== -1; }); function renderHeader() { var owned = branchCards.filter(function(c) { return (col[c.id] || 0) > 0; }).length; var total = branchCards.length; var pct = total > 0 ? Math.round(owned / total * 100) : 0; /* Doublons = copies au-delà de la première */ var dupes = branchCards.reduce(function(s, c) { return s + Math.max(0, (col[c.id] || 0) - 1); }, 0); document.getElementById('coll-pct').textContent = pct + '%'; document.getElementById('coll-count').innerHTML = owned + '<em> / ' + total + '</em>'; document.getElementById('coll-fill').style.width = pct + '%'; document.getElementById('coll-dupes').textContent = dupes; /* Bandeau de détention par rareté */ var strip = document.getElementById('coll-rarity-strip'); if (strip) { var order = ['legendary','epic','rare','common']; strip.innerHTML = order.map(function(rk) { var rar = window.RARITIES[rk]; var cards = branchCards.filter(function(c) { return c.rarity === rk; }); var o = cards.filter(function(c) { return (col[c.id] || 0) > 0; }).length; var t = cards.length; var p = t > 0 ? Math.round(o / t * 100) : 0; return '<div class="crs-item" style="--rc:' + rar.color + ';">' + '<div class="crs-top">' + '<span class="crs-label">' + rarityIcon(rk) + ' ' + rar.label + '</span>' + '<span class="crs-nums">' + '<span class="crs-count">' + o + '<em>/' + t + '</em></span>' + '<span class="crs-pct">' + p + '%</span>' + '</span>' + '</div>' + '<div class="crs-bar"><div class="crs-fill" style="width:' + p + '%"></div></div>' + '</div>'; }).join(''); } } function sortCards(cards) { var sorted = cards.slice(); if (activeSort === 'rarity') { sorted.sort(function(a,b) { return rarityOrder[a.rarity] - rarityOrder[b.rarity] || a.category.localeCompare(b.category) || naturalSort(a.id, b.id); }); } else if (activeSort === 'alpha') { sorted.sort(function(a,b) { return a.category.localeCompare(b.category) || a.name.localeCompare(b.name); }); } return sorted; } function naturalSort(a, b) { var re = /(\d+)/g; var aParts = a.split(re), bParts = b.split(re); for (var i = 0; i < Math.max(aParts.length, bParts.length); i++) { var aSeg = aParts[i] || '', bSeg = bParts[i] || ''; var aNum = parseInt(aSeg,10), bNum = parseInt(bSeg,10); if (!isNaN(aNum) && !isNaN(bNum)) { if (aNum !== bNum) return aNum - bNum; } else { if (aSeg !== bSeg) return aSeg.localeCompare(bSeg); } } return 0; } function renderGrid() { grid.innerHTML = ''; /* Cartes hetero seulement */ var allCards = branchCards.filter(function(c) { var isOwned = (col[c.id] || 0) > 0; var okRarity = activeRarity === 'all' || c.rarity === activeRarity; var okCat = activeCat === 'all' || c.category === activeCat; var okTable = activeTable === 'all' || getCardTable(c) === activeTable; var okOwned = activeOwned === 'all' || (activeOwned === 'owned' && isOwned) || (activeOwned === 'missing' && !isOwned); /* La recherche ne porte que sur les cartes déjà trouvées : révéler le nom d'une carte non possédée casserait l'effet de découverte. */ var okSearch = !searchTerm || (isOwned && ( c.name.toLowerCase().indexOf(searchTerm) !== -1 || window.categoryLabel(c.category).toLowerCase().indexOf(searchTerm) !== -1)); return okRarity && okCat && okTable && okOwned && okSearch; }); var sorted = sortCards(allCards); if (resultsEl) { var ownedShown = sorted.filter(function(c) { return (col[c.id] || 0) > 0; }).length; resultsEl.textContent = sorted.length + ' card' + (sorted.length === 1 ? '' : 's') + ' · ' + ownedShown + ' owned'; } if (!sorted.length) { grid.innerHTML = '<p class="coll-empty">No cards match this filter.</p>'; return; } /* Sequence des cartes possedees, dans l'ordre affiche a l'ecran : c'est elle que parcourent les fleches de la visionneuse. */ ownedSequence = sorted.filter(function(c) { return (col[c.id] || 0) > 0; }); var prevTable = null; sorted.forEach(function(card) { var owned = col[card.id] !== undefined; var rar = window.RARITIES[card.rarity]; var count = col[card.id] || 0; var el = document.createElement('div'); if (owned) { el.className = 'coll-card'; el.style.cssText = 'border-color:' + rar.color + ';box-shadow:0 0 10px ' + rar.glow + ';'; var visuel = FREEZE_THUMBS ? '<canvas class="coll-img coll-canvas" data-src="' + card.file + '"></canvas>' : '<img src="' + card.file + '" class="coll-img" alt="' + card.name + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\';">'; el.innerHTML = visuel + '<div class="coll-img-placeholder" style="display:none;color:' + rar.color + ';">' + rarityIcon(card.rarity) + '</div>' + '<div class="coll-footer">' + '<div class="coll-rarity" style="color:' + rar.color + ';">' + rarityIcon(card.rarity) + ' ' + rar.label + '</div>' + '<div class="coll-cat">' + categoryLabel(card.category) + '</div>' + '<div class="coll-name">' + card.name + '</div>' + (count > 1 ? '<div class="coll-count">×' + count + '</div>' : '') + '</div>'; el.addEventListener('click', function() { openLightbox(card); }); } else { el.className = 'coll-card coll-card-unknown'; el.style.cssText = 'border-color:' + rar.color + ';box-shadow:0 0 8px ' + rar.glow + ';'; el.innerHTML = '<div class="coll-unknown-icon">?</div>' + '<div class="coll-footer">' + '<div class="coll-rarity" style="color:' + rar.color + ';opacity:0.65;">' + rarityIcon(card.rarity) + ' ' + rar.label + '</div>' + '<div class="coll-cat" style="opacity:0.45;">' + categoryLabel(card.category) + '</div>' + '<div class="coll-name coll-name-mystery">???</div>' + '</div>'; } grid.appendChild(el); }); if (FREEZE_THUMBS) chargerVignettes(); } /* ══════════════════════════════════════════ Vignettes figees L'image animee est chargee en memoire mais JAMAIS attachee au DOM : sa 1re frame est dessinee dans un canvas, donc rien ne s'anime. Le chargement se declenche a l'approche de l'ecran (IntersectionObserver) pour eviter de decoder 400+ fichiers d'un coup a l'ouverture. ══════════════════════════════════════════ */ var vignetteObserver = null; function peindreVignette(cv) { if (cv.dataset.done) return; cv.dataset.done = '1'; var src = cv.getAttribute('data-src'); var img = new Image(); img.onload = function() { var w = img.naturalWidth || 300; var h = img.naturalHeight || 400; cv.width = w; cv.height = h; try { cv.getContext('2d').drawImage(img, 0, 0, w, h); } catch (e) { /* Repli : si le canvas echoue, on remet une <img> classique */ var fb = document.createElement('img'); fb.className = 'coll-img'; fb.src = src; if (cv.parentNode) cv.parentNode.replaceChild(fb, cv); } }; img.onerror = function() { cv.style.display = 'none'; var ph = cv.nextElementSibling; if (ph && ph.classList.contains('coll-img-placeholder')) { ph.style.display = 'flex'; } }; img.src = src; } function chargerVignettes() { var cvs = grid.querySelectorAll('canvas.coll-canvas'); if (!cvs.length) return; /* Navigateur ancien sans IntersectionObserver : on peint tout */ if (typeof IntersectionObserver === 'undefined') { Array.prototype.forEach.call(cvs, peindreVignette); return; } if (vignetteObserver) vignetteObserver.disconnect(); vignetteObserver = new IntersectionObserver(function(entries, obs) { entries.forEach(function(en) { if (en.isIntersecting) { peindreVignette(en.target); obs.unobserve(en.target); } }); }, { root: null, rootMargin: '300px', threshold: 0 }); Array.prototype.forEach.call(cvs, function(cv) { vignetteObserver.observe(cv); }); } /* Lightbox */ /* Sequence courante des cartes possedees (remplie par renderGrid) */ var ownedSequence = []; function openLightbox(card) { var old = document.getElementById('coll-lightbox'); if (old) old.remove(); var idx = ownedSequence.indexOf(card); var rar = window.RARITIES[card.rarity]; var lb = document.createElement('div'); lb.id = 'coll-lightbox'; lb.innerHTML = '<div class="lb-backdrop"></div>' + '<div class="lb-content">' + '<button class="lb-close">✕</button>' + '<button class="lb-nav lb-prev" aria-label="Previous">‹</button>' + '<div class="lb-card" style="border-color:' + rar.color + ';box-shadow:0 0 40px ' + rar.glow + ',0 0 80px ' + rar.glow + ';">' + '<img src="' + card.file + '" class="lb-img" alt="' + card.name + '">' + '<div class="lb-footer">' + '<div class="lb-rarity" style="color:' + rar.color + ';">' + rarityIcon(card.rarity) + ' ' + rar.label + '</div>' + '<div class="lb-cat">' + categoryLabel(card.category) + '</div>' + '<div class="lb-name">' + card.name + '</div>' + (ownedSequence.length > 1 && idx !== -1 ? '<div class="lb-counter">' + (idx + 1) + ' / ' + ownedSequence.length + '</div>' : '') + '</div>' + '</div>' + '<button class="lb-nav lb-next" aria-label="Next">›</button>' + '</div>'; document.body.appendChild(lb); var btnPrev = lb.querySelector('.lb-prev'); var btnNext = lb.querySelector('.lb-next'); /* Une seule carte possedee : les fleches n'ont pas lieu d'etre */ if (ownedSequence.length < 2 || idx === -1) { btnPrev.style.display = 'none'; btnNext.style.display = 'none'; } /* Les dimensions réelles ne sont connues qu'une fois l'image chargée : on élargit alors la carte si le visuel est au format paysage. */ var lbCard = lb.querySelector('.lb-card'); var lbImg = lb.querySelector('.lb-img'); function applyOrientation() { if (!lbImg.naturalWidth || !lbImg.naturalHeight) return; if (lbImg.naturalWidth > lbImg.naturalHeight) lbCard.classList.add('lb-card-landscape'); } if (lbImg.complete) applyOrientation(); else lbImg.addEventListener('load', applyOrientation); /* Navigation circulaire dans les cartes possedees */ function go(step) { if (ownedSequence.length < 2 || idx === -1) return; var n = (idx + step + ownedSequence.length) % ownedSequence.length; closeLb(); openLightbox(ownedSequence[n]); } function onKey(e) { if (e.key === 'ArrowRight') go(1); else if (e.key === 'ArrowLeft') go(-1); else if (e.key === 'Escape') closeLb(); } function closeLb() { document.removeEventListener('keydown', onKey); lb.remove(); } btnPrev.addEventListener('click', function(e) { e.stopPropagation(); go(-1); }); btnNext.addEventListener('click', function(e) { e.stopPropagation(); go(1); }); lb.querySelector('.lb-backdrop').addEventListener('click', closeLb); lb.querySelector('.lb-close').addEventListener('click', closeLb); document.addEventListener('keydown', onKey); } /* Listeners */ fRarity.querySelectorAll('.coll-filter').forEach(function(btn) { btn.addEventListener('click', function() { activeRarity = this.getAttribute('data-rarity'); fRarity.querySelectorAll('.coll-filter').forEach(function(b) { b.classList.remove('active'); }); this.classList.add('active'); renderGrid(); }); }); catSel.addEventListener('change', function() { activeCat = this.value; renderGrid(); }); tableSel.addEventListener('change', function() { activeTable = this.value; renderGrid(); }); sortBar.querySelectorAll('.coll-sort').forEach(function(btn) { btn.addEventListener('click', function() { activeSort = this.getAttribute('data-sort'); sortBar.querySelectorAll('.coll-sort').forEach(function(b) { b.classList.remove('active'); }); this.classList.add('active'); renderGrid(); }); }); /* Recherche (cartes possédées uniquement) */ if (searchIn) { searchIn.addEventListener('input', function() { searchTerm = this.value.trim().toLowerCase(); renderGrid(); }); } /* Segment Tout / Possédées / Manquantes */ if (ownedSeg) { ownedSeg.querySelectorAll('.coll-seg-btn').forEach(function(btn) { btn.addEventListener('click', function() { activeOwned = this.getAttribute('data-owned'); ownedSeg.querySelectorAll('.coll-seg-btn').forEach(function(b) { b.classList.remove('active'); }); this.classList.add('active'); renderGrid(); }); }); } /* Réinitialiser tous les filtres d'un coup */ if (resetBtn) { resetBtn.addEventListener('click', function() { activeRarity = 'all'; activeCat = 'all'; activeTable = 'all'; activeOwned = 'all'; searchTerm = ''; catSel.value = 'all'; tableSel.value = 'all'; if (searchIn) searchIn.value = ''; fRarity.querySelectorAll('.coll-filter').forEach(function(b) { b.classList.toggle('active', b.getAttribute('data-rarity') === 'all'); }); ownedSeg.querySelectorAll('.coll-seg-btn').forEach(function(b) { b.classList.toggle('active', b.getAttribute('data-owned') === 'all'); }); renderGrid(); }); } renderHeader(); renderGrid(); })(); window.showFirstVisitTip('collection', { icon: '📚', title: 'Your Collection', body: 'All cards you\'ve ever unlocked are shown here. Cards you haven\'t found yet appear as mystery slots.', bodyScarlett: 'These are the cards you\'ve earned so far. Every mystery slot is something you haven\'t found yet. Keep playing — I have a lot more to show you.', bodyTrans: 'Your little treasure box. Every empty slot is a girl you haven\'t met yet. Keep playing, I promise they\'re worth it.', tips: [ '🔍 Search, or filter by rarity, category and table', '👁️ Switch between All / Owned / Missing', '🖱️ Click a card to view it full size', '×2 ×3 means you own multiple copies' ] }); <</script>> <</done>> <style> /* ══════════════════════════════════════════ MY COLLECTION Identité visuelle : violet/améthyste. (Casino = rose, Card Shop = ambre, Collection = violet) ══════════════════════════════════════════ */ .coll-main { --coll-accent: #c084fc; --coll-glow: rgba(192,132,252,0.4); } /* Garde-fou <br> : SugarCube insère un <br> à chaque retour à la ligne du passage, y compris entre <div>. Dans un conteneur flex/grid il devient un élément à part entière (espaces fantômes ou mise en page décalée). */ .coll-masthead br, .coll-mast-left br, .coll-mast-right br, .coll-prog-head br, .coll-prog-meta br, .coll-rarity-strip br, .coll-toolbar br, .coll-tb-row br, .coll-filters br, .coll-sort-bar br, .coll-seg br, .coll-search-wrap br { display: none; } /* ── Masthead ── */ .coll-masthead { display: flex; justify-content: space-between; align-items: flex-end; gap: 24px; flex-wrap: wrap; padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid rgba(192,132,252,0.18); text-align: left; } .coll-mast-left { flex: 1 1 340px; min-width: 0; } .coll-kicker { font-family: 'Share Tech Mono', monospace; font-size: 0.9rem; letter-spacing: 0.28em; text-transform: uppercase; color: var(--coll-accent); margin-bottom: 8px; } .coll-title { font-family: 'Orbitron', monospace; font-weight: 900; font-size: clamp(1.9rem, 3.4vw, 2.6rem); letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-primary); text-shadow: 0 0 26px rgba(192,132,252,0.25); line-height: 1.1; } .coll-sub { font-family: 'Rajdhani', sans-serif; font-size: 1.1rem; color: var(--text-muted); margin-top: 8px; } /* ── Panneau de progression ── */ .coll-mast-right { min-width: 300px; padding: 18px 22px; border-radius: 12px; background: linear-gradient(160deg, rgba(192,132,252,0.1), rgba(11,8,28,0.6)); border: 1px solid rgba(192,132,252,0.25); } .coll-prog-head { display: flex; justify-content: space-between; align-items: baseline; gap: 14px; } .coll-prog-label { font-family: 'Share Tech Mono', monospace; font-size: 0.82rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-muted); } .coll-prog-pct { font-family: 'Orbitron', monospace; font-weight: 900; font-size: 1.7rem; color: var(--coll-accent); text-shadow: 0 0 14px rgba(192,132,252,0.45); } .coll-prog-count { font-family: 'Orbitron', monospace; font-weight: 700; font-size: 2rem; color: var(--text-primary); line-height: 1.2; margin-top: 6px; } .coll-prog-count em { font-style: normal; font-size: 1.15rem; color: var(--text-muted); } .coll-prog-bar { height: 9px; border-radius: 5px; background: rgba(255,255,255,0.08); overflow: hidden; margin: 12px 0 12px; } .coll-prog-fill { height: 100%; border-radius: 5px; background: linear-gradient(90deg, #7c3aed, #c084fc, #ff2d78); transition: width 0.5s ease; } .coll-prog-meta { display: flex; gap: 16px; flex-wrap: wrap; font-family: 'Share Tech Mono', monospace; font-size: 0.92rem; letter-spacing: 0.05em; color: var(--text-muted); } .cpm-dupes b { font-family: 'Orbitron', monospace; font-size: 1.05rem; color: var(--text-primary); margin-right: 3px; } /* ── Bandeau de détention par rareté ── */ .coll-rarity-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 18px; } .crs-item { padding: 13px 16px; border-radius: 10px; background: rgba(255,255,255,0.025); border: 1px solid rgba(255,255,255,0.07); border-left: 4px solid var(--rc); } .crs-top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 9px; } .crs-label { font-family: 'Orbitron', monospace; font-weight: 700; font-size: 0.88rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--rc); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .crs-nums { display: flex; align-items: baseline; gap: 10px; white-space: nowrap; } .crs-count { font-family: 'Orbitron', monospace; font-weight: 700; font-size: 1.05rem; color: var(--text-primary); white-space: nowrap; } .crs-count em { font-style: normal; font-size: 0.85rem; color: var(--text-muted); } .crs-pct { font-family: 'Share Tech Mono', monospace; font-size: 0.92rem; color: var(--rc); opacity: 0.85; } .crs-bar { height: 6px; border-radius: 3px; background: rgba(255,255,255,0.08); overflow: hidden; } .crs-fill { height: 100%; border-radius: 3px; background: var(--rc); transition: width 0.5s ease; } /* ── Barre d'outils ── */ .coll-toolbar { display: flex; flex-direction: column; gap: 9px; margin-bottom: 16px; padding: 12px 14px; border-radius: 12px; background: rgba(255,255,255,0.022); border: 1px solid rgba(255,255,255,0.06); text-align: left; } .coll-tb-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } .coll-tb-row2 { border-top: 1px solid rgba(255,255,255,0.06); padding-top: 9px; } /* Recherche */ .coll-search-wrap { position: relative; display: flex; align-items: center; flex: 1; min-width: 190px; max-width: 320px; } .coll-search-icon { position: absolute; left: 11px; font-size: 0.85rem; opacity: 0.6; pointer-events: none; } .coll-search { width: 100%; font-family: 'Rajdhani', sans-serif; font-size: 0.9rem; font-weight: 600; padding: 7px 12px 7px 32px; border-radius: 20px; background: var(--dark-card); border: 1px solid rgba(255,255,255,0.1); color: var(--text-primary); transition: border-color 0.2s; } .coll-search::placeholder { color: rgba(150,140,180,0.6); } .coll-search:focus { outline: none; border-color: rgba(192,132,252,0.5); box-shadow: 0 0 12px rgba(192,132,252,0.15); } /* Segment Tout / Possédées / Manquantes */ .coll-seg { display: flex; border-radius: 20px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); flex-shrink: 0; } .coll-seg-btn { font-family: 'Rajdhani', sans-serif; font-weight: 700; font-size: 0.84rem; letter-spacing: 0.05em; padding: 7px 15px; border: none; background: transparent; color: var(--text-muted); cursor: pointer; transition: all 0.2s ease; } .coll-seg-btn + .coll-seg-btn { border-left: 1px solid rgba(255,255,255,0.08); } .coll-seg-btn:hover { color: var(--text-primary); background: rgba(255,255,255,0.05); } .coll-seg-btn.active { background: rgba(192,132,252,0.2); color: #e9d5ff; } /* Compteur de résultats + reset */ .coll-results { font-family: 'Share Tech Mono', monospace; font-size: 0.76rem; color: var(--text-muted); margin-left: auto; white-space: nowrap; } .coll-reset { font-family: 'Rajdhani', sans-serif; font-weight: 700; font-size: 0.8rem; letter-spacing: 0.05em; padding: 6px 13px; border-radius: 18px; border: 1px solid rgba(255,255,255,0.12); background: transparent; color: var(--text-muted); cursor: pointer; transition: all 0.2s ease; flex-shrink: 0; } .coll-reset:hover { color: #ff8ab5; border-color: rgba(255,45,120,0.4); background: rgba(255,45,120,0.08); } /* Dropdowns */ .coll-dropdown { font-family: 'Rajdhani', sans-serif; font-size: 0.88rem; font-weight: 600; letter-spacing: 0.05em; padding: 7px 32px 7px 12px; border-radius: 20px; background: var(--dark-card); border: 1px solid rgba(255,255,255,0.1); color: rgba(210,200,240,0.85); cursor: pointer; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='rgba(192,132,252,0.8)' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; background-color: var(--dark-card); min-width: 142px; transition: border-color 0.2s; } .coll-dropdown:focus { outline: none; border-color: rgba(192,132,252,0.5); } .coll-dropdown option { background: #0e0a1e; color: rgba(220,210,240,0.9); } .coll-sort-label { font-family: 'Share Tech Mono', monospace; font-size: 0.7rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-muted); margin-right: 2px; } /* Séparateur de table dans la grille */ .coll-table-sep { grid-column: 1 / -1; font-family: 'Orbitron', monospace; font-size: 0.72rem; font-weight: 900; letter-spacing: 0.2em; text-transform: uppercase; color: rgba(255,215,0,0.55); padding: 10px 0 4px; border-bottom: 1px solid rgba(255,215,0,0.12); margin-bottom: 4px; } /* Filtres de rareté / tri — repris à l'accent violet de la Collection (ils héritaient du rose global, ce qui brouillait l'identité de l'écran) */ .coll-main .coll-filter:hover, .coll-main .coll-filter.active { border-color: var(--coll-accent); background: rgba(192,132,252,0.14); box-shadow: 0 0 10px rgba(192,132,252,0.2); color: #e9d5ff; } .coll-main .coll-sort:hover { border-color: rgba(192,132,252,0.4); } .coll-main .coll-sort.active { border-color: var(--coll-accent); background: rgba(192,132,252,0.14); color: #e9d5ff; } @media (max-width: 860px) { .coll-rarity-strip { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 620px) { .coll-mast-right { min-width: 0; width: 100%; } .coll-search-wrap { max-width: none; } .coll-results { margin-left: 0; } } </style>
<div class="casino-main"> <div class="section-title" id="pack-title">🎴 Opening Pack...</div> <div id="pack-reveal-area" class="pack-reveal-area-big"></div> <div class="action-bar" id="pack-actions" style="justify-content:center; margin-top:24px; display:none;"></div> </div> <<done>> <<script>> (function() { var v = State.variables; var pending = v.pendingPack; var area = document.getElementById('pack-reveal-area'); var actions = document.getElementById('pack-actions'); var title = document.getElementById('pack-title'); if (!area) return; if (!pending || !pending.length) { if (title) title.textContent = '⚠️ No cards available yet'; area.innerHTML = '<p style="text-align:center;color:var(--text-muted);padding:60px 20px;">This pack has no cards yet.<br>Add cards to CARD_DB to start collecting!</p>'; actions.style.display = 'flex'; var bBack = document.createElement('a'); bBack.className = 'btn btn-stand'; bBack.textContent = '🛍️ Back to Shop'; bBack.addEventListener('click', function() { Engine.play('Card Shop'); }); actions.appendChild(bBack); return; } var pack = PACKS.find(function(p) { return p.id === v.currentPackId; }); var isXXL = !!v.currentPackXXL; if (pack && title) { title.innerHTML = pack.emoji + ' ' + pack.name + (isXXL ? ' <span class="pack-title-xxl">XXL ×' + pending.length + '</span>' : ''); } var currentIndex = 0; var totalCards = pending.length; /* Compteur — pastilles + texte (lisible même sur un pack XXL de 9 cartes) */ var counter = document.createElement('div'); counter.className = 'pack-counter'; area.parentNode.insertBefore(counter, area); function renderCounter(index) { var pips = ''; for (var i = 0; i < totalCards; i++) { pips += '<i class="pc-pip' + (i < index ? ' pc-pip-done' : (i === index ? ' pc-pip-now' : '')) + '"></i>'; } counter.innerHTML = '<span class="pc-pips">' + pips + '</span>' + '<span class="pc-text">' + (index + 1) + ' / ' + totalCards + '</span>'; } renderCounter(0); function showCard(index) { area.innerHTML = ''; renderCounter(index); var cardId = pending[index]; var cardObj = getCard(cardId); /* Carte dos — clic pour retourner */ var wrapper = document.createElement('div'); wrapper.className = 'big-card-wrapper'; var inner = document.createElement('div'); inner.className = 'big-card-inner'; /* Dos */ var back = document.createElement('div'); var _bb = State.variables.activeBranch || 'hetero'; var _binf = (window.BRANCH_INFO && window.BRANCH_INFO[_bb]) || window.BRANCH_INFO.hetero; back.className = 'big-card-back big-card-back-' + _bb; back.style.setProperty('--brand', _binf.color); back.innerHTML = '<div class="bcb-pattern"></div>' + '<div class="bcb-frame">' + '<div class="bcb-stack">' + '<div class="bcb-monogram"><span>21</span><em>DESIRES</em></div>' + '<div class="bcb-band">' + '<img src="' + _binf.visual + '" alt="" ' + 'onerror="this.parentNode.classList.add(\'bcb-band-empty\');">' + '<div class="bcb-band-veil"></div>' + '</div>' + '<div class="bcb-branch">' + '<i></i><span>' + _binf.label + '</span><i></i>' + '</div>' + '</div>' + '</div>' + '<div class="bcb-sheen"></div>' + '<div class="big-card-back-hint">Click to reveal</div>'; /* Face */ var front = document.createElement('div'); front.className = 'big-card-front'; inner.appendChild(back); inner.appendChild(front); wrapper.appendChild(inner); area.appendChild(wrapper); /* Bouton suivant / terminer (caché au départ) */ var nextBtn = document.createElement('button'); nextBtn.className = 'btn btn-primary pack-next-btn'; var isLast = (index === totalCards - 1); nextBtn.textContent = isLast ? '✓ Finish' : 'Next Card →'; nextBtn.style.display = 'none'; area.appendChild(nextBtn); /* Flip au clic sur la carte */ wrapper.addEventListener('click', function() { if (inner.classList.contains('flipped')) return; if (!cardObj) { inner.classList.add('flipped'); nextBtn.style.display = 'block'; return; } var rar = RARITIES[cardObj.rarity]; /* collection[cardId] === 1 → première fois qu'on possède cette carte → NEW collection[cardId] > 1 → doublon → +1 */ var isNew = (v.collection[cardId] === 1); var badge = isNew ? '<div class="reveal-badge badge-new">✦ NEW</div>' : '<div class="reveal-badge badge-dupe">⧉ DUPE +1</div>'; front.innerHTML = '<div class="big-card-face rar-' + cardObj.rarity + '" style="--rar:' + rar.color + ';--rar-glow:' + rar.glow + ';">' + '<div class="bcf-border"></div>' + badge + '<div class="bcf-media">' + '<img src="' + cardObj.file + '" class="bcf-backdrop" alt="" aria-hidden="true" onerror="this.style.display=\'none\';">' + '<img src="' + cardObj.file + '" class="big-card-img" alt="' + cardObj.name + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\';">' + '<div class="big-card-img-placeholder" style="display:none;color:' + rar.color + ';">' + rarityIcon(cardObj.rarity) + '</div>' + '<div class="bcf-shine"></div>' + '</div>' + '<div class="big-card-footer">' + '<span class="big-card-rarity"><i class="bcf-dot"></i>' + rarityIcon(cardObj.rarity) + ' ' + rar.label + '</span>' + '<span class="big-card-name">' + cardObj.name + '</span>' + '</div>' + '</div>'; inner.classList.add('flipped'); nextBtn.style.display = 'block'; }); /* Suivant */ nextBtn.addEventListener('click', function() { currentIndex++; if (currentIndex < totalCards) { showCard(currentIndex); } else { showActions(); } }); } function showActions() { area.innerHTML = ''; counter.style.display = 'none'; var packName = pack ? pack.name : 'Pack'; var packEmoji = pack ? pack.emoji : '🎴'; var packTableObj = pack && window.TABLES ? window.TABLES.find(function(t) { return t.id === pack.table; }) : null; var packTableEmoji = packTableObj ? packTableObj.emoji : '🎫'; if (!v.tokens) v.tokens = { vice:0, boudoir:0, heat:0, slutpit:0, heterovip:0, rainbow:0, gayvip:0, transroom:0, velvet:0, dollhouse:0, herturn:0, trance:0, transvip:0, penthouse:0 }; var tableTokens2 = pack ? (v.tokens[pack.table] || 0) : 0; var againPrice = pack ? window.packPrice(pack, isXXL) : 0; var canAgain = pack && tableTokens2 >= againPrice; /* Conteneur principal */ var wrap = document.createElement('div'); wrap.className = 'pack-end-screen'; /* Message de fin */ var msg = document.createElement('div'); msg.className = 'pack-end-msg'; msg.innerHTML = '<span class="pack-end-emoji">' + packEmoji + '</span>' + '<span class="pack-end-text">Pack opened!</span>'; wrap.appendChild(msg); /* Grille de boutons */ var grid = document.createElement('div'); grid.className = 'pack-end-grid' + (canAgain ? ' pack-end-grid-3' : ' pack-end-grid-2'); /* Bouton 1 — View Collection */ var b1 = document.createElement('a'); b1.className = 'pack-end-btn pack-end-btn-collection'; b1.innerHTML = '<span class="peb-icon">📚</span><span class="peb-label">My Collection</span><span class="peb-sub">See your cards</span>'; b1.addEventListener('click', function() { Engine.play('My Collection'); }); grid.appendChild(b1); /* Bouton 2 — Open Another (même format que celui qu'on vient d'ouvrir) */ if (canAgain) { var b2 = document.createElement('a'); b2.className = 'pack-end-btn pack-end-btn-again'; b2.innerHTML = '<span class="peb-icon">' + packEmoji + '</span>' + '<span class="peb-label">Open Another' + (isXXL ? ' XXL' : '') + '</span>' + '<span class="peb-sub">' + packTableEmoji + ' ' + againPrice + ' tokens</span>'; b2.addEventListener('click', function() { if (openPack(v.currentPackId, false, isXXL)) Engine.play('Open Pack'); }); grid.appendChild(b2); } /* Bouton 3 — Back to Shop */ var b3 = document.createElement('a'); b3.className = 'pack-end-btn pack-end-btn-shop'; b3.innerHTML = '<span class="peb-icon">🛍️</span><span class="peb-label">Card Shop</span><span class="peb-sub">Buy more packs</span>'; b3.addEventListener('click', function() { Engine.play('Card Shop'); }); grid.appendChild(b3); wrap.appendChild(grid); actions.style.display = 'block'; actions.appendChild(wrap); } showCard(0); })(); window.showFirstVisitTip('open_pack', { icon: '🎴', title: 'Opening a Pack', body: 'Click each card to flip it and reveal what\'s inside. Cards range from Common to Legendary rarity.', bodyScarlett: 'Click each card to reveal what\'s inside. You never know what you\'re going to get — that\'s half the fun. Legendaries turn up the least often, so enjoy it when one does.', bodyTrans: 'Go on, flip them one by one. The anticipation is the best part. And if a Legendary shows up, I want to hear you scream.', tips: [ '🖱️ Click the card to reveal it', '✦ NEW means it\'s your first copy', 'DUPE +1 means you already own this card', '💱 Duplicates can be sold in the Sell menu' ] }); <</script>> <</done>>
<div class="game-layout"> <div class="game-table"> <div class="dealer-zone"> <div class="zone-label dealer-label"><span class="zl-icon">🤖</span><span class="zl-text">Dealer</span></div> <<print displayHand($dealerHand, true)>> <div class="hand-total">Visible : <<print calculateHand([$dealerHand[1]])>></div> </div> <div class="player-zone"> <<if $currentHand === "main">> <div class="zone-label player-label"><span class="zl-icon">🎭</span><span class="zl-text">Your hand<<if $isSplit>> (main)<</if>></span></div> <<print displayHand($playerHand, false)>> <<silently>><<set _pv = calculateHand($playerHand)>><</silently>> <<if _pv > 21>> <div class="hand-total bust">💥 <<print _pv>> — BUST!</div> <<if $isSplit>> <<set $currentHand = "split">> <<goto "Play Hand">> <<else>> <<silently>> <<set $gameState = "result">> <<set $totalLosses += 1>> <<set $totalHandsPlayed += 1>> <</silently>> <<goto "Game Result">> <</if>> <<elseif isBlackjack($playerHand) && !$isSplit>> <div class="hand-total blackjack-total">⭐ <<print _pv>> — BLACKJACK!</div> <<silently>><<set $gameState = "dealer">><</silently>> <<goto "Dealer Turn">> <<elseif _pv === 21>> <div class="hand-total" style="color:var(--win-green);text-shadow:0 0 16px var(--win-green);">✦ <<print _pv>> — Standing automatically</div> <<silently>><<set $gameState = "dealer">><</silently>> <<timed 0.9s>> <<if $isSplit>> <<set $currentHand = "split">> <<goto "Play Hand">> <<else>> <<goto "Dealer Turn">> <</if>> <</timed>> <<else>> <div class="hand-total">Total : <<print softDisplayScore($playerHand)>></div> <div class="action-bar"> <<link "🃏 Hit">><<set $playerHand.push(drawCard())>><<goto "Play Hand">><</link>> <<link "✋ Stand">> <<if $isSplit>> <<set $currentHand = "split">> <<goto "Play Hand">> <<else>> <<set $gameState = "dealer">> <<goto "Dealer Turn">> <</if>> <</link>> <<if $playerHand.length === 2 && $canDouble && !$isSplit>> <<link "⬆️ Double Down">> <<set $playerMoney -= $currentBet>> <<set $currentBet *= 2>> <<set $playerHand.push(drawCard())>> <<if calculateHand($playerHand) > 21>> <<set $gameState = "result">> <<set $totalLosses += 1>> <<set $totalHandsPlayed += 1>> <<goto "Game Result">> <<else>> <<set $gameState = "dealer">> <<goto "Dealer Turn">> <</if>> <</link>> <</if>> <<if $playerHand.length === 2 && $canSplit && !$isSplit>> <<link "✂️ Split">> <<set $isSplit = true>> <<set $splitHand.push($playerHand.pop())>> <<set $splitBet = $currentBet>> <<set $playerMoney -= $currentBet>> <<set $playerHand.push(drawCard())>> <<set $splitHand.push(drawCard())>> <<goto "Play Hand">> <</link>> <</if>> </div> <</if>> <<else>> <div class="zone-label player-label"><span class="zl-icon">✂️</span><span class="zl-text">Your hand (Split)</span></div> <<print displayHand($splitHand, false)>> <<silently>><<set _sv = calculateHand($splitHand)>><</silently>> <<if _sv > 21>> <div class="hand-total bust">💥 <<print _sv>> — BUST!</div> <<silently>><<set $gameState = "dealer">><</silently>> <<goto "Dealer Turn">> <<elseif _sv === 21>> <div class="hand-total" style="color:var(--win-green);text-shadow:0 0 16px var(--win-green);">✦ <<print _sv>> — Standing automatically</div> <<silently>><<set $gameState = "dealer">><</silently>> <<timed 0.9s>><<goto "Dealer Turn">><</timed>> <<else>> <div class="hand-total">Total : <<print softDisplayScore($splitHand)>></div> <div class="action-bar"> <<link "🃏 Hit">><<set $splitHand.push(drawCard())>><<goto "Play Hand">><</link>> <<link "✋ Stand">><<set $gameState = "dealer">><<goto "Dealer Turn">><</link>> </div> <</if>> <</if>> </div> </div> </div> <<done>> <<script>> (function() { var _TBG = { 'vice': 'Images/icons/icon_thevice.png', 'boudoir': 'Images/icons/icon_boudoir.png', 'heat': 'Images/icons/icon_heat.png', 'slutpit': 'Images/icons/icon_slutpit.png', 'heterovip': 'Images/icons/icon_heterovip.png', 'rainbow': 'Images/scenes/the_rainbow.png', 'gayvip': 'Images/scenes/the_gay_vip.png', 'transroom': 'Images/icons/icon_transroom.png', 'velvet': 'Images/icons/icon_velvet.png', 'dollhouse': 'Images/icons/icon_dollhouse.png', 'herturn': 'Images/icons/icon_herturn.png', 'trance': 'Images/icons/icon_trance.png', 'transvip': 'Images/icons/icon_transvip.png', 'penthouse': 'Images/scenes/the_penthouse.png' }; (function applyTableBg() { var at = State.variables.activeTable; var img = at && _TBG[at] ? _TBG[at] : null; document.body.style.backgroundImage = img ? 'linear-gradient(rgba(0,0,0,0.62),rgba(0,0,0,0.55),rgba(0,0,0,0.62)), url("' + img + '")' : ''; document.body.style.backgroundSize = 'cover'; document.body.style.backgroundPosition = 'center'; document.body.style.backgroundAttachment = 'fixed'; })(); /* Boutons action — redesign avec icône + label + sous-titre */ const bar = document.querySelector('.action-bar'); if (bar) { bar.querySelectorAll('a').forEach(function(link) { const txt = link.textContent.trim(); link.classList.add('action-btn'); if (txt.includes('Hit')) { link.classList.add('btn', 'btn-hit'); link.innerHTML = '<span class="action-btn-icon">🃏</span>' + '<span class="action-btn-label">Hit</span>' + '<span class="action-btn-sub">Draw a card</span>'; } else if (txt.includes('Stand')) { link.classList.add('btn', 'btn-stand'); link.innerHTML = '<span class="action-btn-icon">✋</span>' + '<span class="action-btn-label">Stand</span>' + '<span class="action-btn-sub">Keep your hand</span>'; } else if (txt.includes('Double')) { link.classList.add('btn', 'btn-gold'); link.innerHTML = '<span class="action-btn-icon">✖️2</span>' + '<span class="action-btn-label">Double Down</span>' + '<span class="action-btn-sub">One card · Double bet</span>'; } else if (txt.includes('Split')) { link.classList.add('btn', 'btn-secondary'); link.innerHTML = '<span class="action-btn-icon">✂️</span>' + '<span class="action-btn-label">Split</span>' + '<span class="action-btn-sub">Two hands</span>'; } }); } /* HUD fixe gauche */ var old = document.getElementById('game-hud'); if (old) old.remove(); var v = State.variables; var hud = document.createElement('div'); hud.id = 'game-hud'; document.getElementById('passages').classList.add('game-page'); hud.innerHTML = '<div class="hud-logo">THE <span>21</span> DESIRES</div>' + '<div class="hud-stat"><span class="hud-stat-label">\u{1F4B0} Balance</span><span class="hud-stat-value">' + formatMoney(v.playerMoney) + '</span></div>' + '<div class="hud-sep"></div>' + '<div class="hud-stat"><span class="hud-stat-label">\u{1F3B0} Bet</span><span class="hud-stat-value bet-color">' + formatMoney(v.currentBet) + '</span></div>' + (v.isSplit ? '<div class="hud-sep"></div><div class="hud-stat"><span class="hud-stat-label">Split</span><span class="hud-stat-value bet-color">' + formatMoney(v.splitBet) + '</span></div>' : '') + '<div class="hud-sep"></div>' + '<div class="hud-stat"><span class="hud-stat-label">\u{1F3AB} Tokens</span><span class="hud-stat-value" style="color:var(--token);text-shadow:0 0 10px var(--token-glow);">' + (v.tokens ? (v.tokens[v.activeTable] || 0) : 0) + '</span></div>' + '<div class="hud-sep"></div><div class="hud-stat"><span class="hud-stat-label">🔥 Streak</span><span class="hud-stat-value" style="color:' + ((v.currentStreak && v.currentStreak[v.activeTable] > 0) ? 'var(--neon-pink);text-shadow:0 0 10px rgba(255,45,120,0.5)' : 'rgba(255,255,255,0.3)') + ';">' + ((v.currentStreak && v.currentStreak[v.activeTable]) || 0) + '</span></div>'; document.body.appendChild(hud); })(); <</script>> <</done>>
<div class="sell-main"> <div class="sell-masthead"> <div class="sell-mast-left"> <div id="sell-brand-bar"></div> <div class="sell-title">Sell Cards</div> <div class="sell-sub">You always keep one copy of every card — only the extras go on the market.</div> </div> <div class="sell-mast-right"> <div class="sell-stat-label">Ready to sell</div> <div class="sell-stat-count" id="sell-summary-count">0<em> cards</em></div> <div class="sell-stat-value" id="sell-summary-value">+$0</div> <button class="sell-confirm-btn" id="sell-all-btn">💱 Sell all duplicates</button> </div> </div> <div class="sell-price-row"> <span class="sell-price-legend">Sell value</span> <span class="sell-price-chip sell-price-common">⚪ Common <b>$10</b></span> <span class="sell-price-chip sell-price-rare">🔵 Rare <b>$30</b></span> <span class="sell-price-chip sell-price-epic">🟣 Epic <b>$80</b></span> <span class="sell-price-chip sell-price-legendary">🌟 Legendary <b>$200</b></span> </div> <div id="sell-grid" class="sell-grid"></div> <div id="sell-empty" class="sell-empty"> <div class="sell-empty-icon">🃏</div> <p>You have no duplicate cards to sell.</p> </div> </div> <<done>> <<script>> (function() { var v = State.variables; var col = v.collection; var grid = document.getElementById('sell-grid'); var emptyEl = document.getElementById('sell-empty'); /* Masquer le bloc vide par défaut via JS (évite les problèmes SugarCube avec style inline) */ if (emptyEl) emptyEl.style.display = 'none'; /* ══════════════════════════════════════════ Vignettes figees (meme principe que My Collection) true = les cartes de la grille sont figees sur leur 1re image. false = comportement d'origine, tout s'anime en permanence. ══════════════════════════════════════════ */ var FREEZE_SELL_THUMBS = true; var sellBrand = document.getElementById('sell-brand-bar'); if (sellBrand && window.branchBanner) sellBrand.innerHTML = window.branchBanner('Sell Cards'); var PRICES = window.SELL_PRICES || { common:5, rare:15, epic:40, legendary:100 }; /* Les mondes sont cloisonnes : on ne vend que les doublons de la branche actuellement active (hetero / gay / trans). */ var BRANCH_TAGS = (window.CATEGORIES || []) .filter(function(c) { return c.branch === v.activeBranch; }) .map(function(c) { return c.tag; }); function getDuplicates() { return window.CARD_DB.filter(function(c) { return BRANCH_TAGS.indexOf(c.category) !== -1 && (col[c.id] || 0) >= 2; }); } function calcTotal(dupes) { return dupes.reduce(function(sum, c) { return sum + ((col[c.id] - 1) * PRICES[c.rarity]); }, 0); } function calcTotalCards(dupes) { return dupes.reduce(function(sum, c) { return sum + (col[c.id] - 1); }, 0); } function updateSummary(dupes) { var totalCards = calcTotalCards(dupes); var totalVal = calcTotal(dupes); var countEl = document.getElementById('sell-summary-count'); var valEl = document.getElementById('sell-summary-value'); var btn = document.getElementById('sell-all-btn'); if (countEl) countEl.innerHTML = totalCards + '<em> card' + (totalCards !== 1 ? 's' : '') + '</em>'; if (valEl) valEl.textContent = '+' + formatMoney(totalVal); if (btn) { btn.disabled = (totalCards === 0); btn.style.opacity = totalCards === 0 ? '0.3' : '1'; btn.style.cursor = totalCards === 0 ? 'not-allowed' : 'pointer'; } } function renderGrid() { grid.innerHTML = ''; var dupes = getDuplicates(); updateSummary(dupes); if (!dupes.length) { if (emptyEl) emptyEl.style.display = 'flex'; grid.style.display = 'none'; return; } if (emptyEl) emptyEl.style.display = 'none'; grid.style.display = ''; dupes.forEach(function(card) { var rar = window.RARITIES[card.rarity]; var count = col[card.id] || 0; var sellable = count - 1; var price = PRICES[card.rarity]; var el = document.createElement('div'); el.className = 'sell-card'; el.style.borderColor = rar.color; var placeholder = document.createElement('div'); placeholder.className = 'sell-card-placeholder'; placeholder.style.cssText = 'display:none;color:' + rar.color; placeholder.textContent = rarityIcon(card.rarity); /* Vignette figee : meme principe que dans My Collection. L'image animee n'entre jamais dans le DOM, seule sa 1re frame est peinte dans un canvas -> aucune animation. */ var img; if (FREEZE_SELL_THUMBS) { img = document.createElement('canvas'); img.className = 'sell-card-img sell-canvas'; img.setAttribute('data-src', card.file); } else { img = document.createElement('img'); img.src = card.file; img.className = 'sell-card-img'; img.alt = card.name; img.onerror = function() { this.style.display = 'none'; placeholder.style.display = 'flex'; }; } var badge = document.createElement('div'); badge.className = 'sell-card-count-badge'; badge.textContent = '\u00d7' + count; var imgWrap = document.createElement('div'); imgWrap.className = 'sell-card-img-wrap'; imgWrap.appendChild(img); imgWrap.appendChild(placeholder); imgWrap.appendChild(badge); var body = document.createElement('div'); body.className = 'sell-card-body'; body.innerHTML = '<div class="sell-card-rarity" style="color:' + rar.color + '">' + rarityIcon(card.rarity) + ' ' + rar.label + '</div>' + '<div class="sell-card-name">' + card.name + '</div>' + '<div class="sell-card-meta">' + '<span>\u00d7' + sellable + ' to sell</span>' + '<span class="sell-card-gain">+' + formatMoney(sellable * price) + '</span>' + '</div>'; el.appendChild(imgWrap); el.appendChild(body); grid.appendChild(el); }); if (FREEZE_SELL_THUMBS) chargerVignettesSell(); } /* ══════════════════════════════════════════ Peinture des vignettes figees L'image est chargee en memoire sans jamais entrer dans le DOM : sa 1re frame est dessinee dans un canvas, donc rien ne s'anime. Chargement a l'approche de l'ecran pour eviter de decoder toutes les cartes d'un coup. ══════════════════════════════════════════ */ var sellObserver = null; function peindreVignetteSell(cv) { if (cv.dataset.done) return; cv.dataset.done = '1'; var src = cv.getAttribute('data-src'); var im = new Image(); im.onload = function() { var w = im.naturalWidth || 300; var h = im.naturalHeight || 400; cv.width = w; cv.height = h; try { cv.getContext('2d').drawImage(im, 0, 0, w, h); } catch (e) { var fb = document.createElement('img'); fb.className = 'sell-card-img'; fb.src = src; if (cv.parentNode) cv.parentNode.replaceChild(fb, cv); } }; im.onerror = function() { cv.style.display = 'none'; var ph = cv.parentNode && cv.parentNode.querySelector('.sell-card-placeholder'); if (ph) ph.style.display = 'flex'; }; im.src = src; } function chargerVignettesSell() { var cvs = grid.querySelectorAll('canvas.sell-canvas'); if (!cvs.length) return; if (typeof IntersectionObserver === 'undefined') { Array.prototype.forEach.call(cvs, peindreVignetteSell); return; } if (sellObserver) sellObserver.disconnect(); sellObserver = new IntersectionObserver(function(entries, obs) { entries.forEach(function(en) { if (en.isIntersecting) { peindreVignetteSell(en.target); obs.unobserve(en.target); } }); }, { root: null, rootMargin: '300px', threshold: 0 }); Array.prototype.forEach.call(cvs, function(cv) { sellObserver.observe(cv); }); } /* Sell all button */ document.getElementById('sell-all-btn').addEventListener('click', function() { var dupes = getDuplicates(); var totalGain = calcTotal(dupes); if (totalGain === 0) return; dupes.forEach(function(card) { col[card.id] = 1; }); v.playerMoney += totalGain; if (typeof buildNavSidebar === 'function') buildNavSidebar(); renderGrid(); }); renderGrid(); })(); window.showFirstVisitTip('sell_cards', { icon: '💱', title: 'Sell Duplicates', body: 'Got multiples of the same card? Sell the extras for cash. You always keep at least one copy of each card.', bodyScarlett: 'Got doubles? Sell the extras for cash. I always make sure you keep at least one copy of each card — I\'m generous like that.', bodyTrans: 'Too many copies of the same girl? Sell the extras for cash, darling. You always keep one of each, I make sure of it.', tips: [ '⚪ Common → $10 each', '🔵 Rare → $30 each', '🟣 Epic → $80 each', '🌟 Legendary → $200 each' ] }); <</script>> <</done>> <style> /* ══════════════════════════════════════════ SELL CARDS ══════════════════════════════════════════ */ .sell-main { --sell-accent: #00f5ff; } /* Garde-fou <br> : SugarCube insère un <br> à chaque retour à la ligne du passage, y compris entre <div>. Dans un conteneur flex il devient un élément à part entière (espaces fantômes). */ .sell-masthead br, .sell-mast-left br, .sell-mast-right br, .sell-price-row br, .sell-grid br { display: none; } /* ── Masthead ── */ .sell-masthead { display: flex; justify-content: space-between; align-items: flex-end; gap: 24px; flex-wrap: wrap; padding-bottom: 16px; margin-bottom: 16px; border-bottom: 1px solid rgba(0,245,255,0.18); text-align: left; } .sell-mast-left { flex: 1 1 340px; min-width: 0; } .sell-kicker { font-family: 'Share Tech Mono', monospace; font-size: 0.9rem; letter-spacing: 0.28em; text-transform: uppercase; color: var(--sell-accent); margin-bottom: 8px; } .sell-title { font-family: 'Orbitron', monospace; font-weight: 900; font-size: clamp(1.9rem, 3.4vw, 2.6rem); letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-primary); text-shadow: 0 0 26px rgba(0,245,255,0.22); line-height: 1.1; } .sell-sub { font-family: 'Rajdhani', sans-serif; font-size: 1.1rem; color: var(--text-muted); margin-top: 8px; max-width: 520px; } /* ── Panneau "prêt à vendre" ── */ .sell-mast-right { min-width: 300px; padding: 18px 22px; border-radius: 12px; background: linear-gradient(160deg, rgba(0,245,255,0.1), rgba(8,16,28,0.6)); border: 1px solid rgba(0,245,255,0.25); text-align: left; } .sell-stat-label { font-family: 'Share Tech Mono', monospace; font-size: 0.82rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-muted); } .sell-stat-count { font-family: 'Orbitron', monospace; font-weight: 700; font-size: 2rem; color: var(--text-primary); line-height: 1.2; margin-top: 6px; } .sell-stat-count em { font-style: normal; font-size: 1.15rem; color: var(--text-muted); } .sell-stat-value { font-family: 'Orbitron', monospace; font-weight: 900; font-size: 1.7rem; color: var(--sell-accent); text-shadow: 0 0 14px rgba(0,245,255,0.45); margin: 4px 0 14px; } /* ── Barre des prix par rareté ── */ .sell-price-row { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 18px; padding: 13px 18px; background: rgba(0,245,255,0.03); border: 1px solid rgba(0,245,255,0.1); border-radius: 12px; } .sell-price-legend { font-family: 'Share Tech Mono', monospace; font-size: 0.82rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-muted); margin-right: 4px; } .sell-price-chip { font-family: 'Rajdhani', sans-serif; font-weight: 600; font-size: 0.98rem; letter-spacing: 0.04em; padding: 7px 16px; border-radius: 20px; display: inline-flex; align-items: baseline; gap: 8px; } .sell-price-chip b { font-family: 'Orbitron', monospace; font-weight: 700; font-size: 1.02rem; } .sell-price-common { background: rgba(160,160,176,0.1); color:#a0a0b0; border:1px solid rgba(160,160,176,0.2); } .sell-price-rare { background: rgba(79,195,247,0.08); color:#4fc3f7; border:1px solid rgba(79,195,247,0.2); } .sell-price-epic { background: rgba(191,0,255,0.08); color:#bf00ff; border:1px solid rgba(191,0,255,0.2); } .sell-price-legendary{ background: rgba(255,215,0,0.08); color:var(--gold); border:1px solid rgba(255,215,0,0.25); } .sell-confirm-btn { width: 100%; font-family: 'Orbitron', monospace; font-size: 0.92rem; font-weight: 900; letter-spacing: 0.1em; padding: 13px 24px; border-radius: 10px; background: linear-gradient(135deg, rgba(0,180,200,0.3), rgba(0,245,255,0.2)); border: 1px solid rgba(0,245,255,0.4); color: var(--neon-cyan); cursor: pointer; transition: all 0.2s; text-shadow: 0 0 8px rgba(0,245,255,0.3); } .sell-confirm-btn:hover:not(:disabled) { background: linear-gradient(135deg, rgba(0,200,220,0.4), rgba(0,245,255,0.3)); box-shadow: 0 0 20px rgba(0,245,255,0.25); transform: translateY(-2px); } .sell-confirm-btn:disabled { opacity: 0.3; cursor: not-allowed; transform: none; } /* Grille */ .sell-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; } .sell-card { background: var(--dark-card); border: 2px solid; border-radius: 12px; overflow: hidden; } .sell-card-img-wrap { position: relative; width: 100%; aspect-ratio: 2/3; overflow: hidden; } .sell-card-img { width: 100%; height: 100%; object-fit: cover; display: block; } .sell-card-placeholder { width: 100%; height: 100%; display: none; align-items: center; justify-content: center; font-size: 2rem; background: var(--dark-panel); } .sell-card-count-badge { position: absolute; top: 5px; right: 5px; font-family: 'Orbitron', monospace; font-size: 0.65rem; font-weight: 900; padding: 2px 7px; border-radius: 20px; background: rgba(6,4,15,0.88); color: rgba(255,255,255,0.9); border: 1px solid rgba(255,255,255,0.2); backdrop-filter: blur(4px); } .sell-card-body { padding: 8px 10px 10px; display: flex; flex-direction: column; gap: 4px; } .sell-card-rarity { font-family: 'Share Tech Mono', monospace; font-size: 0.68rem; letter-spacing: 0.08em; } .sell-card-name { font-family: 'Rajdhani', sans-serif; font-size: 0.85rem; font-weight: 700; color: var(--text-primary); line-height: 1.2; } .sell-card-meta { display: flex; justify-content: space-between; align-items: center; font-family: 'Share Tech Mono', monospace; font-size: 0.68rem; color: rgba(210,200,235,0.45); margin-top: 2px; } .sell-card-gain { color: var(--neon-cyan); font-weight: bold; } /* Empty state */ .sell-empty { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 60px 20px; text-align: center; } .sell-empty-icon { font-size: 3rem; opacity: 0.25; } .sell-empty p { font-family: 'Rajdhani', sans-serif; font-size: 1rem; color: rgba(210,200,235,0.3); } </style>
<div class="spin-wheel-page"> <div class="spin-wheel-header"> <div class="spin-wheel-visual" id="spin-wheel-visual"></div> <h1 class="spin-wheel-title">🎡 Lucky Wheel</h1> <p class="spin-wheel-subtitle" id="spin-wheel-subtitle">Spin to win</p> </div> <div class="spin-wheel-arena"> <div class="spin-wheel-pointer"><span class="swp-stem"></span><span class="swp-head"></span></div> <svg id="spin-wheel-svg" width="360" height="360" viewBox="0 0 360 360"></svg> <!-- Overlay résultat centré sur/au-dessus de la roue --> <div class="spin-result-overlay" id="spin-result-box"> <div class="spin-result-icon" id="spin-result-icon">🎉</div> <div class="spin-result-label">Your reward</div> <div class="spin-result-value" id="spin-result-value"></div> <div class="spin-result-card" id="spin-result-card" style="display:none"></div> <a class="btn-claim" id="spin-claim-btn">✦ Claim & Continue</a> </div> </div> <div class="spin-wheel-btn-wrap"> <a class="btn-spin" id="spin-btn">SPIN THE WHEEL</a> </div> </div> <<done>> <<script>> (function() { var v = State.variables; var branch = v.activeBranch || 'hetero'; /* Sous-titre selon la branche */ var subtitles = { hetero:'Straight Edition', gay:'Gay Edition', trans:'Trans Edition' }; var sub = document.getElementById('spin-wheel-subtitle'); if (sub) sub.textContent = subtitles[branch] || 'Spin to win'; /* Visuel de la branche, pour savoir d'un coup d'oeil ou l'on joue */ var swVisual = document.getElementById('spin-wheel-visual'); var swInfo = (window.BRANCH_INFO && window.BRANCH_INFO[branch]) || null; if (swVisual && swInfo) { swVisual.style.setProperty('--brand', swInfo.color); swVisual.innerHTML = '<div class="sw-visual-frame">' + '<img src="' + swInfo.visual + '" alt="' + swInfo.label + '" ' + 'onerror="this.style.display=\'none\';this.parentNode.classList.add(\'sw-visual-fallback\');">' + '<span class="sw-visual-emoji">' + swInfo.emoji + '</span>' + '</div>'; } /* Récupérer les segments de la branche */ var segments = (window.WHEEL_SEGMENTS && window.WHEEL_SEGMENTS[branch]) ? window.WHEEL_SEGMENTS[branch] : window.WHEEL_SEGMENTS.hetero; var n = segments.length; /* 10 */ /* ── Construire le SVG ── */ var svg = document.getElementById('spin-wheel-svg'); var size = 420; svg.setAttribute('width', size); svg.setAttribute('height', size); svg.setAttribute('viewBox', '0 0 ' + size + ' ' + size); var cx = size / 2, cy = size / 2; var rOuter = size / 2 - 10; /* rayon des segments */ var rInner = 34; /* rayon du hub central */ var TAU = Math.PI * 2; var angleStep = TAU / n; /* ── Palette resserrée : deux tons profonds alternés + accent jackpot. Dix teintes saturées faisaient "roue de kermesse" ; deux familles de violet/magenta sur presque-noir tiennent mieux le thème. ── */ function segColors(seg, i) { if (seg.type === 'card') return ['#3d0a5c', '#5c1287']; /* pack : violet */ if (seg.value >= 500) return ['#7a0033', '#c4005e']; /* jackpot */ if (seg.type === 'tokens') return ['#0d2b3d', '#14425c']; /* jetons : bleu nuit */ return (i % 2 === 0) ? ['#1a0d2e', '#2b1650'] : ['#12081f', '#1f0f3a']; } var defs = '<defs>'; for (var i = 0; i < n; i++) { var c = segColors(segments[i], i); defs += '<linearGradient id="sg' + i + '" x1="0%" y1="0%" x2="0%" y2="100%">' + '<stop offset="0%" stop-color="' + c[1] + '"/>' + '<stop offset="100%" stop-color="' + c[0] + '"/>' + '</linearGradient>'; } /* Dégradé métallique pour la jante */ defs += '<linearGradient id="rimGold" x1="0%" y1="0%" x2="100%" y2="100%">' + '<stop offset="0%" stop-color="#7a5c12"/>' + '<stop offset="25%" stop-color="#ffe9a8"/>' + '<stop offset="50%" stop-color="#c9a227"/>' + '<stop offset="75%" stop-color="#ffe9a8"/>' + '<stop offset="100%" stop-color="#7a5c12"/>' + '</linearGradient>'; /* Voile lumineux du haut, pour donner du volume au disque */ defs += '<radialGradient id="sheen" cx="50%" cy="18%" r="62%">' + '<stop offset="0%" stop-color="#ffffff" stop-opacity="0.10"/>' + '<stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>' + '</radialGradient>'; defs += '</defs>'; var svgContent = defs; /* ── Fond du disque ── */ svgContent += '<circle cx="' + cx + '" cy="' + cy + '" r="' + (rOuter + 9) + '" fill="#07040f"/>'; /* ── Segments ── */ for (var i = 0; i < n; i++) { var startA = i * angleStep - Math.PI / 2; var endA = (i + 1) * angleStep - Math.PI / 2; var x1 = cx + rOuter * Math.cos(startA); var y1 = cy + rOuter * Math.sin(startA); var x2 = cx + rOuter * Math.cos(endA); var y2 = cy + rOuter * Math.sin(endA); var d = 'M ' + cx.toFixed(1) + ' ' + cy.toFixed(1) + ' L ' + x1.toFixed(1) + ' ' + y1.toFixed(1) + ' A ' + rOuter + ' ' + rOuter + ' 0 0 1 ' + x2.toFixed(1) + ' ' + y2.toFixed(1) + ' Z'; svgContent += '<path d="' + d + '" fill="url(#sg' + i + ')"' + ' stroke="rgba(201,162,39,0.22)" stroke-width="1"/>'; } /* ── Voile lumineux par-dessus les segments ── */ svgContent += '<circle cx="' + cx + '" cy="' + cy + '" r="' + rOuter + '" fill="url(#sheen)"/>'; /* ── Libellés ── Le texte suit le rayon, mais on le retourne de 180° dans la moitié basse : sans ça la moitié des récompenses s'affiche à l'envers. ── */ for (var i = 0; i < n; i++) { var midA = (i + 0.5) * angleStep - Math.PI / 2; var tDist = rOuter * 0.62; var tx = cx + tDist * Math.cos(midA); var ty = cy + tDist * Math.sin(midA); var rotDeg = (i + 0.5) * (360 / n); var flipped = (rotDeg > 90 && rotDeg < 270); if (flipped) rotDeg += 180; /* Découper le libellé en valeur (grande) + unité (petite) */ var seg = segments[i], big = '', small = '', star = false; var lbl = seg.label.replace('🌟', '').trim(); if (seg.label.indexOf('🌟') > -1) star = true; if (seg.type === 'money') { big = lbl; small = ''; } else if (seg.type === 'tokens') { big = String(seg.value); small = 'TOKENS'; } else if (seg.type === 'card') { big = String(seg.value); small = 'PACK'; } else { big = lbl; } var isJackpot = seg.value >= 500; var bigFill = isJackpot ? '#ffe9a8' : '#f2ecff'; var bigSize = isJackpot ? 21 : 18; svgContent += '<g transform="rotate(' + rotDeg.toFixed(1) + ',' + tx.toFixed(1) + ',' + ty.toFixed(1) + ')">'; var yBig = small ? ty - 6 : ty; /* Ombre puis texte, pour tenir la lisibilité sur fond sombre */ svgContent += '<text x="' + tx.toFixed(1) + '" y="' + (yBig + 1).toFixed(1) + '"' + ' text-anchor="middle" dominant-baseline="middle" fill="rgba(0,0,0,0.75)"' + ' font-family="Orbitron,monospace" font-weight="900" font-size="' + bigSize + '">' + big + '</text>'; svgContent += '<text x="' + tx.toFixed(1) + '" y="' + yBig.toFixed(1) + '"' + ' text-anchor="middle" dominant-baseline="middle" fill="' + bigFill + '"' + ' font-family="Orbitron,monospace" font-weight="900" font-size="' + bigSize + '">' + big + '</text>'; if (small) { svgContent += '<text x="' + tx.toFixed(1) + '" y="' + (ty + 11).toFixed(1) + '"' + ' text-anchor="middle" dominant-baseline="middle" fill="rgba(255,255,255,0.5)"' + ' font-family="Orbitron,monospace" font-weight="700" font-size="8"' + ' letter-spacing="1.4">' + small + '</text>'; } if (star) { svgContent += '<text x="' + tx.toFixed(1) + '" y="' + (ty + 15).toFixed(1) + '"' + ' text-anchor="middle" dominant-baseline="middle" fill="#ffd700" font-size="11">★</text>'; } svgContent += '</g>'; } /* ── Séparateurs ── */ for (var i = 0; i < n; i++) { var a = i * angleStep - Math.PI / 2; var lx = cx + rInner * Math.cos(a), ly = cy + rInner * Math.sin(a); var lx2 = cx + rOuter * Math.cos(a), ly2 = cy + rOuter * Math.sin(a); svgContent += '<line x1="' + lx.toFixed(1) + '" y1="' + ly.toFixed(1) + '"' + ' x2="' + lx2.toFixed(1) + '" y2="' + ly2.toFixed(1) + '"' + ' stroke="rgba(201,162,39,0.35)" stroke-width="1"/>'; } /* ── Jante métallique ── */ svgContent += '<circle cx="' + cx + '" cy="' + cy + '" r="' + (rOuter + 5) + '"' + ' fill="none" stroke="url(#rimGold)" stroke-width="9"/>'; svgContent += '<circle cx="' + cx + '" cy="' + cy + '" r="' + (rOuter + 10) + '"' + ' fill="none" stroke="rgba(0,0,0,0.55)" stroke-width="2"/>'; svgContent += '<circle cx="' + cx + '" cy="' + cy + '" r="' + rOuter + '"' + ' fill="none" stroke="rgba(0,0,0,0.5)" stroke-width="1.5"/>'; /* ── Clous sur la jante, un par frontière de segment ── */ for (var i = 0; i < n; i++) { var a = i * angleStep - Math.PI / 2; var px = cx + (rOuter + 5) * Math.cos(a); var py = cy + (rOuter + 5) * Math.sin(a); svgContent += '<circle cx="' + px.toFixed(1) + '" cy="' + py.toFixed(1) + '" r="3.2"' + ' fill="#2a1f05" stroke="rgba(255,233,168,0.85)" stroke-width="1"/>'; svgContent += '<circle cx="' + (px - 0.7).toFixed(1) + '" cy="' + (py - 0.7).toFixed(1) + '" r="1"' + ' fill="rgba(255,255,255,0.75)"/>'; } /* ── Moyeu central ── */ svgContent += '<circle cx="' + cx + '" cy="' + cy + '" r="' + (rInner + 5) + '"' + ' fill="#07040f" stroke="rgba(201,162,39,0.3)" stroke-width="1"/>'; svgContent += '<circle cx="' + cx + '" cy="' + cy + '" r="' + rInner + '"' + ' fill="#0d0718" stroke="url(#rimGold)" stroke-width="2.5"/>'; svgContent += '<circle cx="' + cx + '" cy="' + cy + '" r="' + (rInner - 7) + '"' + ' fill="none" stroke="rgba(201,162,39,0.22)" stroke-width="1"/>'; svgContent += '<text x="' + cx + '" y="' + (cy + 1) + '"' + ' text-anchor="middle" dominant-baseline="middle"' + ' fill="#c9a227" font-family="Orbitron,monospace" font-weight="900"' + ' font-size="15" letter-spacing="1">21</text>'; svg.innerHTML = svgContent; /* ── Logique de spin ── */ var currentRotation = 0; var spinning = false; var spinBtn = document.getElementById('spin-btn'); spinBtn.addEventListener('click', function() { if (spinning) return; spinning = true; spinBtn.classList.add('disabled'); /* Tirer le segment gagnant */ var winIndex = Math.floor(Math.random() * n); var seg = segments[winIndex]; /* Calculer la rotation cible */ /* Pour que le segment winIndex soit sous le pointeur (haut = 270° = -90°) */ /* Le pointeur est à 0° dans le repère SVG (haut), segments commencent à -90° */ /* Angle du centre du segment winIndex dans la rotation actuelle */ var segCenterDeg = (winIndex + 0.5) * (360 / n); /* depuis le top, sens horaire */ /* On veut ramener ce segment au pointeur (0°) → rotation = -segCenterDeg */ /* + plusieurs tours complets pour l'effet dramatique */ var fullSpins = 5 + Math.floor(Math.random() * 3); var targetRot = currentRotation + (fullSpins * 360) + (360 - segCenterDeg); /* Normaliser pour éviter drift infini */ currentRotation = targetRot; svg.style.transition = 'transform 4.5s cubic-bezier(0.17, 0.67, 0.12, 1.0)'; svg.style.transform = 'rotate(' + targetRot + 'deg)'; /* Après la fin de l'animation */ setTimeout(function() { showResult(seg, winIndex); }, 4700); }); /* ── Affichage du résultat ── */ function showResult(seg, idx) { var box = document.getElementById('spin-result-box'); var iconEl = document.getElementById('spin-result-icon'); var valueEl = document.getElementById('spin-result-value'); var cardDiv = document.getElementById('spin-result-card'); var claimBtn = document.getElementById('spin-claim-btn'); v._wheelRewardType = seg.type; v._wheelRewardValue = seg.value; if (seg.type === 'money') { iconEl.textContent = '💰'; valueEl.textContent = '+' + formatMoney(seg.value); valueEl.style.color = '#ffd700'; claimBtn.style.display = ''; claimBtn.textContent = '✦ Claim & Continue'; claimBtn.onclick = function() { v.playerMoney += v._wheelRewardValue; finalizeClaim(); }; } else if (seg.type === 'tokens') { iconEl.textContent = '🎫'; valueEl.textContent = '+' + seg.value + ' tokens'; valueEl.style.color = 'var(--neon-cyan)'; claimBtn.style.display = 'none'; /* Afficher le sélecteur de table après un court délai */ setTimeout(function() { showTablePicker('tokens', seg.value, box); }, 400); } else if (seg.type === 'card') { iconEl.textContent = '🎴'; valueEl.textContent = '1 Pack'; valueEl.style.color = '#d966ff'; claimBtn.style.display = 'none'; setTimeout(function() { showTablePicker('pack', 1, box); }, 400); } box.style.display = 'flex'; box.style.animation = 'resultReveal 0.4s cubic-bezier(0.34,1.1,0.64,1) forwards'; /* Bloquer la sidebar pour empêcher de naviguer sans claim */ document.querySelectorAll('#nav-sidebar .nsb-btn, #nav-sidebar a').forEach(function(el) { el.style.pointerEvents = 'none'; el.style.opacity = '0.25'; }); } /* ── Sélecteur de table ── */ function showTablePicker(rewardType, rewardValue, box) { /* Supprimer un éventuel picker existant */ var old = document.getElementById('wheel-table-picker'); if (old) old.remove(); /* Tables disponibles selon la branche */ var branchTables = window.TABLES.filter(function(t) { if (branch === 'hetero') return t.branch === 'hetero'; if (branch === 'gay') return t.branch === 'gay'; if (branch === 'trans') return t.branch === 'trans'; return false; }); var picker = document.createElement('div'); picker.id = 'wheel-table-picker'; picker.className = 'wtp-overlay'; var title = rewardType === 'tokens' ? '🎫 +' + rewardValue + ' tokens — Choose your table' : '🎴 1 Free Pack — Choose your table'; var rows = branchTables.map(function(t) { /* Vérifier si la table est débloquée */ var isUnlocked = ( branch === 'hetero' ? v.unlockedTables.indexOf(t.id) !== -1 : branch === 'gay' ? v.unlockedTablesGay.indexOf(t.id) !== -1 : v.unlockedTablesTrans.indexOf(t.id) !== -1 ); /* Compter cartes possédées vs total pour ce pack */ var pack = window.PACKS.find(function(p) { return p.table === t.id && !p.forcedRarity; }); var owned = 0, total = 0; if (pack) { total = window.CARD_DB.filter(function(c) { return pack.categories.indexOf(c.category) !== -1; }).length; owned = window.CARD_DB.filter(function(c) { return pack.categories.indexOf(c.category) !== -1 && v.collection[c.id] !== undefined; }).length; } var pct = total > 0 ? Math.round(owned / total * 100) : 0; /* Tables scellees (MYSTERY_TABLES) : nom et emoji masques. Retirer l'id de window.MYSTERY_TABLES le devoile ici aussi. */ if ((window.mysteryTables ? window.mysteryTables() : []).indexOf(t.id) !== -1) { return '<div class="wtp-row wtp-row-locked">' + '<span class="wtp-emoji" style="filter:grayscale(1) opacity(0.4)">❔</span>' + '<span class="wtp-name" style="opacity:0.35">Sealed table</span>' + '<span class="wtp-locked-badge">🔒 Coming soon</span>' + '</div>'; } if (!isUnlocked) { return '<div class="wtp-row wtp-row-locked">' + '<span class="wtp-emoji" style="filter:grayscale(1) opacity(0.4)">' + t.emoji + '</span>' + '<span class="wtp-name" style="opacity:0.35">' + t.name + '</span>' + '<span class="wtp-locked-badge">🔒 Not unlocked</span>' + '</div>'; } return '<button class="wtp-row" data-table="' + t.id + '">' + '<span class="wtp-emoji">' + t.emoji + '</span>' + '<span class="wtp-name">' + t.name + '</span>' + '<span class="wtp-progress">' + '<span class="wtp-count">' + owned + '/' + total + '</span>' + '<span class="wtp-bar"><span class="wtp-bar-fill" style="width:' + pct + '%"></span></span>' + '</span>' + '</button>'; }).join(''); picker.innerHTML = '<div class="wtp-box">' + '<div class="wtp-title">' + title + '</div>' + '<div class="wtp-subtitle">Your collection progress per table :</div>' + '<div class="wtp-list">' + rows + '</div>' + '</div>'; /* Insérer dans la page (pas dans l'overlay roue) */ document.body.appendChild(picker); /* Listeners sur chaque ligne */ picker.querySelectorAll('button.wtp-row').forEach(function(btn) { btn.addEventListener('click', function() { var tableId = this.getAttribute('data-table'); if (rewardType === 'tokens') { if (!v.tokens) v.tokens = {}; v.tokens[tableId] = (v.tokens[tableId] || 0) + rewardValue; picker.remove(); finalizeClaim(); } else { /* Pack : ouvrir un pack de la table choisie */ var pack = window.PACKS.find(function(p) { return p.table === tableId && !p.forcedRarity; }); picker.remove(); if (pack && window.openPack) { var result = window.openPack(pack.id, true); if (result === true || result === 'free') { finalizeClaim(true); /* true = ne pas rediriger casino, aller Open Pack */ } else { finalizeClaim(); } } else { finalizeClaim(); } } }); }); } function finalizeClaim(openPackPage) { /* Réactiver la sidebar */ document.querySelectorAll('#nav-sidebar .nsb-btn, #nav-sidebar a').forEach(function(el) { el.style.pointerEvents = ''; el.style.opacity = ''; }); v.wheelHandsCount[v.activeBranch] = 0; v.wheelReady[v.activeBranch] = false; delete v._wheelRewardType; delete v._wheelRewardValue; delete v._wheelCardId; if (openPackPage) { Engine.play('Open Pack'); } else { Engine.play('Casino'); } } /* Claim simple (argent) */ var claimBtn = document.getElementById('spin-claim-btn'); if (claimBtn && !claimBtn._bound) { claimBtn._bound = true; } /* Appliquer le fond de table */ document.body.style.backgroundImage = ''; document.body.style.backgroundSize = ''; document.body.style.backgroundPosition = ''; })(); window.showFirstVisitTip('spin_wheel', { icon: '🎡', title: 'Lucky Wheel', body: 'The Lucky Wheel unlocks every 20 hands played. Spin it to win cash, tokens or a free pack — always a positive reward.', bodyScarlett: 'Every 20 hands, I give you a little gift. Spin it — there\'s no losing here. Consider it my way of keeping you at the table.', bodyTrans: 'Every 20 hands, the wheel is my treat. Spin it, there\'s no way to lose. A girl likes to spoil her regulars.', tips: [ '🎡 Unlocks every 20 hands, automatically', '💰 Cash rewards go directly to your balance', '🎫 Tokens — choose which table gets them', '🎴 Pack reward — choose your table too' ] }); <</script>> <</done>>
/* ══════════════════════════════════════════ ★ REGLAGE DE VERSION — a verifier avant « Publier dans un fichier » ★ true = version PATREON (monde Trans ouvert) false = version PUBLIQUE (monde Trans affiche « Early access on Patreon », cartes Trans retirees de tous les totaux) ══════════════════════════════════════════ */ <<set setup.TRANS_ENABLED = false>> /* ══════════════════════════════════════════ SETUP : VALEURS DES CARTES & CONFIG ══════════════════════════════════════════ */ <<set setup.cardValues = { "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "10": 10, "j": 10, "q": 10, "k": 10, "ace": 11 }>> <<set setup.suits = ["spades", "clubs", "diamonds", "hearts"]>> <<set setup.ranks = ["2","3","4","5","6","7","8","9","10","j","q","k","ace"]>> /* ══════════════════════════════════════════ FONCTIONS JAVASCRIPT ══════════════════════════════════════════ */ <<script>> /* Tirer une carte aléatoire (sabot infini) */ window.drawCard = function() { const suit = setup.suits[Math.floor(Math.random() * 4)]; const rank = setup.ranks[Math.floor(Math.random() * 13)]; return { rank, suit }; }; /* Calculer la valeur d'une main (gestion As) */ window.calculateHand = function(hand) { let value = 0, aces = 0; hand.forEach(card => { value += setup.cardValues[card.rank]; if (card.rank === "ace") aces++; }); while (value > 21 && aces > 0) { value -= 10; aces--; } return value; }; /* Affichage du score : X/Y quand main souple (As valant 11), sinon juste la valeur */ window.softDisplayScore = function(hand) { var hard = 0; hand.forEach(function(c) { hard += (c.rank === 'ace') ? 1 : setup.cardValues[c.rank]; }); var soft = calculateHand(hand); /* Main souple = soft > hard, soft <= 21, pas un blackjack */ if (soft > hard && soft <= 21 && !isBlackjack(hand)) { return hard + ' / ' + soft; } return '' + soft; }; /* Afficher une carte (image) */ window.displayCard = function(card, hidden = false) { if (hidden) { return `<div class="card card-back"> <div class="card-inner">🂠</div> </div>`; } const suitSymbols = { spades: "♠", clubs: "♣", diamonds: "♦", hearts: "♥" }; const rankDisplay = { "ace": "A", "j": "J", "q": "Q", "k": "K" }; const r = rankDisplay[card.rank] || card.rank; const s = suitSymbols[card.suit]; const isRed = (card.suit === "diamonds" || card.suit === "hearts"); const colorClass = isRed ? "card-red" : "card-black"; return `<div class="card ${colorClass}"> <div class="card-corner card-tl">${r}<br>${s}</div> <div class="card-center">${s}</div> <div class="card-corner card-br">${r}<br>${s}</div> </div>`; }; /* Afficher une main complète */ window.displayHand = function(hand, hideFirst = false) { let html = '<div class="hand-display">'; hand.forEach((card, i) => { html += displayCard(card, hideFirst && i === 0); }); html += '</div>'; return html; }; /* Peut-on splitter ? */ window.canSplit = function(hand) { if (hand.length !== 2) return false; return setup.cardValues[hand[0].rank] === setup.cardValues[hand[1].rank]; }; /* Blackjack naturel = As + carte valant 10, en 2 cartes */ window.isBlackjack = function(hand) { if (hand.length !== 2) return false; const hasAce = hand.some(c => c.rank === "ace"); const hasTen = hand.some(c => setup.cardValues[c.rank] === 10 && c.rank !== "ace"); return hasAce && hasTen; }; /* Formater l'argent joliment */ window.formatMoney = function(n) { return "$" + n.toLocaleString(); }; /* ══════════════════════════════════════════ DONNÉES : RARETÉS ══════════════════════════════════════════ */ window.RARITIES = { common: { label:"Common", color:"#a0a0b0", glow:"rgba(160,160,176,0.4)" }, rare: { label:"Rare", color:"#4fc3f7", glow:"rgba(79,195,247,0.5)" }, epic: { label:"Epic", color:"#bf00ff", glow:"rgba(191,0,255,0.5)" }, legendary: { label:"Legendary", color:"#ffd700", glow:"rgba(255,215,0,0.6)" } }; /* ══════════════════════════════════════════ TRANSMUTATION On desassemble des doublons pour obtenir de l'Essence, puis on depense cette Essence pour obtenir a coup sur une carte NON POSSEDEE de la rarete choisie. Couts calibres pour rester legerement plus chers que le hasard : l'interet est la garantie, pas le rendement. ══════════════════════════════════════════ */ window.ESSENCE_YIELD = { common:5, rare:15, epic:40, legendary:100 }; window.ESSENCE_COST = { common:40, rare:80, epic:200, legendary:600 }; /* Prix de revente par rareté */ window.SELL_PRICES = { common:10, rare:30, epic:80, legendary:200 }; /* ══════════════════════════════════════════ BRANCHES hetero : progression indépendante gay : progression indépendante, unlock via bouton ══════════════════════════════════════════ */ window.BRANCHES = { hetero: { id:"hetero", label:"Straight", earnedVar:"earnedHetero" }, gay: { id:"gay", label:"Gay", earnedVar:"earnedGay" }, trans: { id:"trans", label:"Trans", earnedVar:"earnedTrans" } }; /* ══════════════════════════════════════════ TABLES branch : "hetero" | "gay" | "both" threshold : earnedXxx requis (0 = toujours ouverte dans sa branche) ══════════════════════════════════════════ */ window.TABLES = [ /* ── BRANCHE HÉTÉRO ── */ { id:"vice", branch:"hetero", name:"The Vice", emoji:"🃏", threshold:0, minBet:10, maxBet:250, color:"linear-gradient(90deg,#ff2d78,#bf00ff)" }, { id:"boudoir", branch:"hetero", name:"The Boudoir", emoji:"💋", threshold:5000, minBet:25, maxBet:500, color:"linear-gradient(90deg,#ff69b4,#ff2d78)" }, { id:"heat", branch:"hetero", name:"The Heat", emoji:"🔥", threshold:20000, minBet:50, maxBet:1000, color:"linear-gradient(90deg,#ff6600,#ff2d78)" }, { id:"slutpit", branch:"hetero", name:"The Slut Pit", emoji:"💀", threshold:30000, minBet:75, maxBet:1500, color:"linear-gradient(90deg,#8b0000,#ff2d78)" }, { id:"heterovip", branch:"hetero", name:"Hetero VIP", emoji:"👑", threshold:75000, minBet:100, maxBet:2500, color:"linear-gradient(90deg,#ffd700,#ff2d78)" }, /* ── BRANCHE GAY ── */ { id:"rainbow", branch:"gay", name:"The Rainbow", emoji:"🌈", threshold:0, minBet:10, maxBet:250, color:"linear-gradient(90deg,#a855f7,#3b82f6)" }, { id:"gayvip", branch:"gay", name:"Gay VIP", emoji:"🏳️🌈", threshold:30000, minBet:100, maxBet:2500, color:"linear-gradient(90deg,#ffd700,#a855f7)" }, /* ── BRANCHE TRANS ── */ { id:"transroom", branch:"trans", name:"The Powder Room",emoji:"💜", threshold:0, minBet:10, maxBet:250, color:"linear-gradient(90deg,#f9a8d4,#a855f7)" }, { id:"velvet", branch:"trans", name:"The Velvet", emoji:"🌹", threshold:5000, minBet:25, maxBet:500, color:"linear-gradient(90deg,#f9a8d4,#be123c)" }, { id:"dollhouse", branch:"trans", name:"The Dollhouse",emoji:"🎀", threshold:20000, minBet:50, maxBet:1000, color:"linear-gradient(90deg,#f472b6,#a855f7)" }, { id:"herturn", branch:"trans", name:"Her Turn", emoji:"👑", threshold:30000, minBet:50, maxBet:1500, color:"linear-gradient(90deg,#a855f7,#be123c)" }, { id:"trance", branch:"trans", name:"The Trance", emoji:"🌀", threshold:50000, minBet:75, maxBet:2000, color:"linear-gradient(90deg,#c084fc,#f9a8d4)" }, { id:"transvip", branch:"trans", name:"Trans VIP", emoji:"🏳️⚧️", threshold:75000, minBet:100, maxBet:2500, color:"linear-gradient(90deg,#ffd700,#f9a8d4)" }, /* ── VIP ULTIME ── */ { id:"penthouse", branch:"both", name:"The Penthouse", emoji:"💎", threshold:200000, minBet:500, maxBet:5000, color:"linear-gradient(90deg,#ffd700,#00f5ff)" } ]; /* BUILD : PUBLIC OU PATREON — reglage en tete de ce passage (StoryInit) : <<set setup.TRANS_ENABLED = true / false>>. Le jour ou Trans devient public pour tous : laisser a true. */ window.TRANS_ENABLED = (setup.TRANS_ENABLED !== false); window.PATREON_URL = 'https://patreon.com/Draggora_Studio?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink'; window.GAME_VERSION = '0.1.3'; /* ══════════════════════════════════════════ CATÉGORIES — tag → label + branch ══════════════════════════════════════════ */ window.CATEGORIES = [ /* Hétéro */ { tag:"girl_strip", label:"Girl Strip", branch:"hetero" }, { tag:"girl_ass", label:"Girl Ass", branch:"hetero" }, { tag:"tits", label:"Tits", branch:"hetero" }, { tag:"pussy_eating", label:"Pussy Eating", branch:"hetero" }, { tag:"handjob", label:"Handjob", branch:"hetero" }, { tag:"handjob_cum", label:"Handjob Cum", branch:"hetero" }, { tag:"anal", label:"Anal", branch:"hetero" }, { tag:"blowjob", label:"Blowjob", branch:"hetero" }, { tag:"fingering", label:"Fingering", branch:"hetero" }, { tag:"fucking", label:"Fucking", branch:"hetero" }, { tag:"tits_fuck", label:"Tits Fuck", branch:"hetero" }, { tag:"rimjob", label:"Rimjob", branch:"hetero" }, { tag:"dp", label:"DP", branch:"hetero" }, { tag:"bbc", label:"BBC", branch:"hetero" }, { tag:"creampie", label:"Creampie", branch:"hetero" }, { tag:"threesome", label:"Threesome", branch:"hetero" }, { tag:"gangbang", label:"Gangbang", branch:"hetero" }, { tag:"cumshot", label:"Cumshot", branch:"hetero" }, /* Gay */ { tag:"gay_solo", label:"Gay Solo", branch:"gay" }, { tag:"gay_scenes", label:"Gay Scenes", branch:"gay" }, /* Trans */ { tag:"ts_tease", label:"TS Tease", branch:"trans" }, { tag:"ts_solo", label:"TS Solo", branch:"trans" }, { tag:"ts_handjob", label:"TS Handjob", branch:"trans" }, { tag:"ts_fingering", label:"TS Fingering", branch:"trans" }, { tag:"ts_sucks", label:"TS Sucks", branch:"trans" }, { tag:"ts_cum_face", label:"TS Cum Face", branch:"trans" }, { tag:"ts_fucked", label:"TS Fucked", branch:"trans" }, { tag:"ts_bbc", label:"TS BBC", branch:"trans" }, { tag:"ts_threesome", label:"TS Threesome", branch:"trans" }, { tag:"ts_creampie", label:"TS Creampie", branch:"trans" }, { tag:"ts_sucked", label:"TS Sucked", branch:"trans" }, { tag:"ts_fucks_guy", label:"TS Tops", branch:"trans" }, { tag:"ts_with_girls", label:"TS With Girls", branch:"trans" }, { tag:"ts_hypno", label:"TS Hypno", branch:"trans" } ]; /* ══════════════════════════════════════════ PAQUETS categories[] : tags autorisés dans ce pack forcedRarity : si défini, toutes les cartes ont cette rareté ══════════════════════════════════════════ */ window.PACKS = [ /* ── THE VICE ── */ { id:"vice_pack", table:"vice", branch:"hetero", price:50, name:"Vice Pack", emoji:"🃏", desc:"Girl Strip · Girl Ass · Tits · Pussy Eating", categories:["girl_strip","girl_ass","tits","pussy_eating"] }, /* ── THE BOUDOIR ── */ { id:"boudoir_pack", table:"boudoir", branch:"hetero", price:50, name:"Boudoir Pack", emoji:"💋", desc:"Pussy Eating · Handjob · Blowjob · Fingering · Tit Fuck", categories:["pussy_eating","handjob","handjob_cum","fingering","tits_fuck","blowjob"] }, /* ── THE HEAT ── */ { id:"heat_pack", table:"heat", branch:"hetero", price:50, name:"Heat Pack", emoji:"🔥", desc:"Anal · Blowjob · Handjob Cum · Fingering · Fucking · Tit Fuck", categories:["anal","blowjob","handjob_cum","fingering","fucking","tits_fuck"] }, /* ── THE SLUT PIT ── */ { id:"slutpit_pack", table:"slutpit", branch:"hetero", price:50, name:"Slut Pit Pack", emoji:"💀", desc:"BBC · Creampie · Threesome · Gangbang · Rimjob · DP · Cumshot", categories:["bbc","creampie","threesome","gangbang","rimjob","dp","cumshot"] }, /* ── HETERO VIP ── */ { id:"heterovip_pack", table:"heterovip", branch:"hetero", price:80, name:"Hetero VIP Pack", emoji:"👑", desc:"Exclusive hetero content — all categories", categories:["girl_strip","girl_ass","tits","pussy_eating","handjob","anal","blowjob","handjob_cum","fingering","tits_fuck","fucking","rimjob","dp","bbc","creampie","threesome","gangbang","cumshot"] }, { id:"heterovip_legendary", table:"heterovip", branch:"hetero", price:200, name:"Hetero Legendary Pack", emoji:"🌟", desc:"Guaranteed Legendary — hetero only", categories:["girl_strip","girl_ass","tits","pussy_eating","handjob","anal","blowjob","handjob_cum","fingering","tits_fuck","fucking","rimjob","dp","bbc","creampie","threesome","gangbang","cumshot"], forcedRarity:"legendary" }, /* ── THE RAINBOW ── */ { id:"rainbow_pack", table:"rainbow", branch:"gay", price:50, name:"Rainbow Pack", emoji:"🌈", desc:"Gay Solo · Gay Scenes", categories:["gay_solo","gay_scenes"] }, /* ── GAY VIP ── */ { id:"gayvip_pack", table:"gayvip", branch:"gay", price:80, name:"Gay VIP Pack", emoji:"🏳️🌈", desc:"Exclusive gay content — all categories", categories:["gay_solo","gay_scenes"] }, { id:"gayvip_legendary", table:"gayvip", branch:"gay", price:200, name:"Gay Legendary Pack", emoji:"🌟", desc:"Guaranteed Legendary — gay only", categories:["gay_solo","gay_scenes"], forcedRarity:"legendary" }, /* ── THE TRANS ROOM ── */ { id:"trans_pack", table:"transroom", branch:"trans", price:50, name:"Powder Room Pack", emoji:"💜", desc:"TS Tease · TS Solo · TS Handjob", categories:["ts_tease","ts_solo","ts_handjob"] }, /* ── TRANS VIP ── */ { id:"velvet_pack", table:"velvet", branch:"trans", price:50, name:"Velvet Pack", emoji:"🌹", desc:"TS Solo · TS Handjob · TS Fingering · TS Sucks · TS Cum Face · TS Fucked", categories:["ts_solo","ts_handjob","ts_fingering","ts_sucks","ts_cum_face","ts_fucked"] }, { id:"dollhouse_pack", table:"dollhouse", branch:"trans", price:50, name:"Dollhouse Pack", emoji:"🎀", desc:"TS Fucked · TS BBC · TS Threesome · TS Creampie", categories:["ts_fucked","ts_bbc","ts_threesome","ts_creampie"] }, { id:"herturn_pack", table:"herturn", branch:"trans", price:50, name:"Her Turn Pack", emoji:"👑", desc:"TS Sucked · TS Tops · TS With Girls", categories:["ts_sucked","ts_fucks_guy","ts_with_girls"] }, { id:"trance_pack", table:"trance", branch:"trans", price:50, name:"Trance Pack", emoji:"🌀", desc:"TS Hypno · TS Tease", categories:["ts_hypno","ts_tease"] }, { id:"transvip_pack", table:"transvip", branch:"trans", price:80, name:"Trans VIP Pack", emoji:"🏳️⚧️", desc:"Exclusive trans content — all categories", categories:["ts_tease","ts_solo","ts_handjob","ts_fingering","ts_sucks","ts_cum_face","ts_fucked","ts_bbc","ts_threesome","ts_creampie","ts_sucked","ts_fucks_guy","ts_with_girls","ts_hypno"] }, { id:"transvip_legendary", table:"transvip", branch:"trans", price:200, name:"Trans Legendary Pack", emoji:"🌟", desc:"Guaranteed Legendary — trans only", categories:["ts_tease","ts_solo","ts_handjob","ts_fingering","ts_sucks","ts_cum_face","ts_fucked","ts_bbc","ts_threesome","ts_creampie","ts_sucked","ts_fucks_guy","ts_with_girls","ts_hypno"], forcedRarity:"legendary" }, /* ── THE PENTHOUSE (VIP ULTIME) ── */ { id:"penthouse_pack", table:"penthouse", branch:"both", price:100, name:"Penthouse Pack", emoji:"💎", desc:"Wild Card — all categories from both branches", categories:["girl_strip","girl_ass","tits","pussy_eating","handjob","anal","blowjob","handjob_cum","fingering","tits_fuck","fucking","rimjob","dp","bbc","creampie","threesome","gangbang","cumshot","gay_solo","gay_scenes","ts_tease","ts_solo"] }, { id:"penthouse_legendary", table:"penthouse", branch:"both", price:250, name:"Penthouse Legendary Pack", emoji:"💎🌟", desc:"Guaranteed Legendary — all categories", categories:["girl_strip","girl_ass","tits","pussy_eating","handjob","anal","blowjob","handjob_cum","fingering","tits_fuck","fucking","rimjob","dp","bbc","creampie","threesome","gangbang","cumshot","gay_solo","gay_scenes","ts_tease","ts_solo"], forcedRarity:"legendary" } ]; /* ══════════════════════════════════════════ CARD_DB — vide au démarrage Ajouter les cartes ici au format : { id:"girl_strip_001", name:"Girl Strip #001", rarity:"common", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_001.webp" } ══════════════════════════════════════════ */ window.CARD_DB = [ /* ── GIRL STRIP ── */ { id:"girl_strip_1", name:"Girl Strip #1", rarity:"common", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_1.webp" }, { id:"girl_strip_2", name:"Girl Strip #2", rarity:"common", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_2.webp" }, { id:"girl_strip_3", name:"Girl Strip #3", rarity:"rare", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_3.webp" }, { id:"girl_strip_4", name:"Girl Strip #4", rarity:"common", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_4.webp" }, { id:"girl_strip_5", name:"Girl Strip #5", rarity:"common", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_5.webp" }, { id:"girl_strip_6", name:"Girl Strip #6", rarity:"epic", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_6.webp" }, { id:"girl_strip_7", name:"Girl Strip #7", rarity:"rare", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_7.webp" }, { id:"girl_strip_8", name:"Girl Strip #8", rarity:"legendary", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_8.webp" }, { id:"girl_strip_9", name:"Girl Strip #9", rarity:"common", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_9.webp" }, { id:"girl_strip_10", name:"Girl Strip #10", rarity:"common", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_10.webp" }, { id:"girl_strip_11", name:"Girl Strip #11", rarity:"common", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_11.webp" }, { id:"girl_strip_12", name:"Girl Strip #12", rarity:"epic", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_12.webp" }, { id:"girl_strip_13", name:"Girl Strip #13", rarity:"common", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_13.webp" }, { id:"girl_strip_14", name:"Girl Strip #14", rarity:"rare", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_14.webp" }, { id:"girl_strip_15", name:"Girl Strip #15", rarity:"common", category:"girl_strip", file:"Images/cards/girl_strip/girl_strip_15.webp" }, /* ── GIRL ASS ── */ { id:"girl_ass_1", name:"Girl Ass #1", rarity:"rare", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_1.webp" }, { id:"girl_ass_2", name:"Girl Ass #2", rarity:"epic", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_2.webp" }, { id:"girl_ass_3", name:"Girl Ass #3", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_3.webp" }, { id:"girl_ass_4", name:"Girl Ass #4", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_4.webp" }, { id:"girl_ass_5", name:"Girl Ass #5", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_5.webp" }, { id:"girl_ass_6", name:"Girl Ass #6", rarity:"legendary", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_6.webp" }, { id:"girl_ass_7", name:"Girl Ass #7", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_7.webp" }, { id:"girl_ass_8", name:"Girl Ass #8", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_8.webp" }, { id:"girl_ass_9", name:"Girl Ass #9", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_9.webp" }, { id:"girl_ass_10", name:"Girl Ass #10", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_10.webp" }, { id:"girl_ass_11", name:"Girl Ass #11", rarity:"epic", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_11.webp" }, { id:"girl_ass_12", name:"Girl Ass #12", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_12.webp" }, { id:"girl_ass_13", name:"Girl Ass #13", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_13.webp" }, { id:"girl_ass_14", name:"Girl Ass #14", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_14.webp" }, { id:"girl_ass_15", name:"Girl Ass #15", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_15.webp" }, { id:"girl_ass_16", name:"Girl Ass #16", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_16.webp" }, { id:"girl_ass_17", name:"Girl Ass #17", rarity:"epic", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_17.webp" }, { id:"girl_ass_18", name:"Girl Ass #18", rarity:"rare", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_18.webp" }, { id:"girl_ass_19", name:"Girl Ass #19", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_19.webp" }, { id:"girl_ass_20", name:"Girl Ass #20", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_20.webp" }, { id:"girl_ass_21", name:"Girl Ass #21", rarity:"rare", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_21.webp" }, { id:"girl_ass_22", name:"Girl Ass #22", rarity:"rare", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_22.webp" }, { id:"girl_ass_23", name:"Girl Ass #23", rarity:"rare", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_23.webp" }, { id:"girl_ass_24", name:"Girl Ass #24", rarity:"rare", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_24.webp" }, { id:"girl_ass_25", name:"Girl Ass #25", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_25.webp" }, { id:"girl_ass_26", name:"Girl Ass #26", rarity:"rare", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_26.webp" }, { id:"girl_ass_27", name:"Girl Ass #27", rarity:"rare", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_27.webp" }, { id:"girl_ass_28", name:"Girl Ass #28", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_28.webp" }, { id:"girl_ass_29", name:"Girl Ass #29", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_29.webp" }, { id:"girl_ass_30", name:"Girl Ass #30", rarity:"rare", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_30.webp" }, { id:"girl_ass_31", name:"Girl Ass #31", rarity:"epic", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_31.webp" }, { id:"girl_ass_32", name:"Girl Ass #32", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_32.webp" }, { id:"girl_ass_33", name:"Girl Ass #33", rarity:"rare", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_33.webp" }, { id:"girl_ass_34", name:"Girl Ass #34", rarity:"epic", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_34.webp" }, { id:"girl_ass_35", name:"Girl Ass #35", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_35.webp" }, { id:"girl_ass_36", name:"Girl Ass #36", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_36.webp" }, { id:"girl_ass_37", name:"Girl Ass #37", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_37.webp" }, { id:"girl_ass_38", name:"Girl Ass #38", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_38.webp" }, { id:"girl_ass_39", name:"Girl Ass #39", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_39.webp" }, { id:"girl_ass_40", name:"Girl Ass #40", rarity:"common", category:"girl_ass", file:"Images/cards/girl_ass/girl_ass_40.webp" }, /* ── TITS ── */ { id:"tits_1", name:"Tits #1", rarity:"common", category:"tits", file:"Images/cards/tits/tits_1.webp" }, { id:"tits_2", name:"Tits #2", rarity:"common", category:"tits", file:"Images/cards/tits/tits_2.webp" }, { id:"tits_3", name:"Tits #3", rarity:"rare", category:"tits", file:"Images/cards/tits/tits_3.webp" }, { id:"tits_4", name:"Tits #4", rarity:"rare", category:"tits", file:"Images/cards/tits/tits_4.webp" }, { id:"tits_5", name:"Tits #5", rarity:"common", category:"tits", file:"Images/cards/tits/tits_5.webp" }, { id:"tits_6", name:"Tits #6", rarity:"epic", category:"tits", file:"Images/cards/tits/tits_6.webp" }, { id:"tits_7", name:"Tits #7", rarity:"epic", category:"tits", file:"Images/cards/tits/tits_7.webp" }, { id:"tits_8", name:"Tits #8", rarity:"rare", category:"tits", file:"Images/cards/tits/tits_8.webp" }, { id:"tits_9", name:"Tits #9", rarity:"common", category:"tits", file:"Images/cards/tits/tits_9.webp" }, { id:"tits_10", name:"Tits #10", rarity:"common", category:"tits", file:"Images/cards/tits/tits_10.webp" }, { id:"tits_11", name:"Tits #11", rarity:"common", category:"tits", file:"Images/cards/tits/tits_11.webp" }, { id:"tits_12", name:"Tits #12", rarity:"rare", category:"tits", file:"Images/cards/tits/tits_12.webp" }, { id:"tits_13", name:"Tits #13", rarity:"rare", category:"tits", file:"Images/cards/tits/tits_13.webp" }, { id:"tits_14", name:"Tits #14", rarity:"common", category:"tits", file:"Images/cards/tits/tits_14.webp" }, { id:"tits_15", name:"Tits #15", rarity:"common", category:"tits", file:"Images/cards/tits/tits_15.webp" }, { id:"tits_16", name:"Tits #16", rarity:"common", category:"tits", file:"Images/cards/tits/tits_16.webp" }, { id:"tits_17", name:"Tits #17", rarity:"common", category:"tits", file:"Images/cards/tits/tits_17.webp" }, { id:"tits_18", name:"Tits #18", rarity:"common", category:"tits", file:"Images/cards/tits/tits_18.webp" }, { id:"tits_19", name:"Tits #19", rarity:"rare", category:"tits", file:"Images/cards/tits/tits_19.webp" }, { id:"tits_20", name:"Tits #20", rarity:"common", category:"tits", file:"Images/cards/tits/tits_20.webp" }, { id:"tits_21", name:"Tits #21", rarity:"common", category:"tits", file:"Images/cards/tits/tits_21.webp" }, { id:"tits_22", name:"Tits #22", rarity:"common", category:"tits", file:"Images/cards/tits/tits_22.webp" }, { id:"tits_23", name:"Tits #23", rarity:"common", category:"tits", file:"Images/cards/tits/tits_23.webp" }, { id:"tits_24", name:"Tits #24", rarity:"epic", category:"tits", file:"Images/cards/tits/tits_24.webp" }, { id:"tits_25", name:"Tits #25", rarity:"rare", category:"tits", file:"Images/cards/tits/tits_25.webp" }, { id:"tits_26", name:"Tits #26", rarity:"common", category:"tits", file:"Images/cards/tits/tits_26.webp" }, { id:"tits_27", name:"Tits #27", rarity:"common", category:"tits", file:"Images/cards/tits/tits_27.webp" }, { id:"tits_28", name:"Tits #28", rarity:"rare", category:"tits", file:"Images/cards/tits/tits_28.webp" }, { id:"tits_29", name:"Tits #29", rarity:"common", category:"tits", file:"Images/cards/tits/tits_29.webp" }, { id:"tits_30", name:"Tits #30", rarity:"common", category:"tits", file:"Images/cards/tits/tits_30.webp" }, { id:"tits_31", name:"Tits #31", rarity:"epic", category:"tits", file:"Images/cards/tits/tits_31.webp" }, { id:"tits_32", name:"Tits #32", rarity:"common", category:"tits", file:"Images/cards/tits/tits_32.webp" }, { id:"tits_33", name:"Tits #33", rarity:"common", category:"tits", file:"Images/cards/tits/tits_33.webp" }, { id:"tits_34", name:"Tits #34", rarity:"legendary", category:"tits", file:"Images/cards/tits/tits_34.webp" }, { id:"tits_35", name:"Tits #35", rarity:"common", category:"tits", file:"Images/cards/tits/tits_35.webp" }, { id:"tits_36", name:"Tits #36", rarity:"common", category:"tits", file:"Images/cards/tits/tits_36.webp" }, { id:"tits_37", name:"Tits #37", rarity:"rare", category:"tits", file:"Images/cards/tits/tits_37.webp" }, { id:"tits_38", name:"Tits #38", rarity:"epic", category:"tits", file:"Images/cards/tits/tits_38.webp" }, { id:"tits_39", name:"Tits #39", rarity:"common", category:"tits", file:"Images/cards/tits/tits_39.webp" }, { id:"tits_40", name:"Tits #40", rarity:"common", category:"tits", file:"Images/cards/tits/tits_40.webp" }, { id:"tits_41", name:"Tits #41", rarity:"common", category:"tits", file:"Images/cards/tits/tits_41.webp" }, { id:"tits_42", name:"Tits #42", rarity:"rare", category:"tits", file:"Images/cards/tits/tits_42.webp" }, /* ── FUCKING ── */ { id:"fucking_1", name:"Fucking #1", rarity:"rare", category:"fucking", file:"Images/cards/fuck/fucking_1.webp" }, { id:"fucking_2", name:"Fucking #2", rarity:"epic", category:"fucking", file:"Images/cards/fuck/fucking_2.webp" }, { id:"fucking_3", name:"Fucking #3", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_3.webp" }, { id:"fucking_4", name:"Fucking #4", rarity:"epic", category:"fucking", file:"Images/cards/fuck/fucking_4.webp" }, { id:"fucking_5", name:"Fucking #5", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_5.webp" }, { id:"fucking_6", name:"Fucking #6", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_6.webp" }, { id:"fucking_7", name:"Fucking #7", rarity:"rare", category:"fucking", file:"Images/cards/fuck/fucking_7.webp" }, { id:"fucking_8", name:"Fucking #8", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_8.webp" }, { id:"fucking_9", name:"Fucking #9", rarity:"epic", category:"fucking", file:"Images/cards/fuck/fucking_9.webp" }, { id:"fucking_10", name:"Fucking #10", rarity:"rare", category:"fucking", file:"Images/cards/fuck/fucking_10.webp" }, { id:"fucking_11", name:"Fucking #11", rarity:"epic", category:"fucking", file:"Images/cards/fuck/fucking_11.webp" }, { id:"fucking_12", name:"Fucking #12", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_12.webp" }, { id:"fucking_13", name:"Fucking #13", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_13.webp" }, { id:"fucking_14", name:"Fucking #14", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_14.webp" }, { id:"fucking_15", name:"Fucking #15", rarity:"rare", category:"fucking", file:"Images/cards/fuck/fucking_15.webp" }, { id:"fucking_16", name:"Fucking #16", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_16.webp" }, { id:"fucking_17", name:"Fucking #17", rarity:"rare", category:"fucking", file:"Images/cards/fuck/fucking_17.webp" }, { id:"fucking_18", name:"Fucking #18", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_18.webp" }, { id:"fucking_19", name:"Fucking #19", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_19.webp" }, { id:"fucking_20", name:"Fucking #20", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_20.webp" }, { id:"fucking_21", name:"Fucking #21", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_21.webp" }, { id:"fucking_22", name:"Fucking #22", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_22.webp" }, { id:"fucking_23", name:"Fucking #23", rarity:"rare", category:"fucking", file:"Images/cards/fuck/fucking_23.webp" }, { id:"fucking_24", name:"Fucking #24", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_24.webp" }, { id:"fucking_25", name:"Fucking #25", rarity:"legendary", category:"fucking", file:"Images/cards/fuck/fucking_25.webp" }, { id:"fucking_26", name:"Fucking #26", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_26.webp" }, { id:"fucking_27", name:"Fucking #27", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_27.webp" }, { id:"fucking_28", name:"Fucking #28", rarity:"rare", category:"fucking", file:"Images/cards/fuck/fucking_28.webp" }, { id:"fucking_29", name:"Fucking #29", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_29.webp" }, { id:"fucking_30", name:"Fucking #30", rarity:"rare", category:"fucking", file:"Images/cards/fuck/fucking_30.webp" }, { id:"fucking_31", name:"Fucking #31", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_31.webp" }, { id:"fucking_32", name:"Fucking #32", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_32.webp" }, { id:"fucking_33", name:"Fucking #33", rarity:"common", category:"fucking", file:"Images/cards/fuck/fucking_33.webp" }, /* ── ANAL ── */ { id:"anal_1", name:"Anal #1", rarity:"common", category:"anal", file:"Images/cards/anal/anal_1.webp" }, { id:"anal_2", name:"Anal #2", rarity:"epic", category:"anal", file:"Images/cards/anal/anal_2.webp" }, { id:"anal_3", name:"Anal #3", rarity:"legendary", category:"anal", file:"Images/cards/anal/anal_3.webp" }, { id:"anal_4", name:"Anal #4", rarity:"common", category:"anal", file:"Images/cards/anal/anal_4.webp" }, { id:"anal_5", name:"Anal #5", rarity:"common", category:"anal", file:"Images/cards/anal/anal_5.webp" }, { id:"anal_6", name:"Anal #6", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_6.webp" }, { id:"anal_7", name:"Anal #7", rarity:"common", category:"anal", file:"Images/cards/anal/anal_7.webp" }, { id:"anal_8", name:"Anal #8", rarity:"common", category:"anal", file:"Images/cards/anal/anal_8.webp" }, { id:"anal_9", name:"Anal #9", rarity:"common", category:"anal", file:"Images/cards/anal/anal_9.webp" }, { id:"anal_10", name:"Anal #10", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_10.webp" }, { id:"anal_11", name:"Anal #11", rarity:"common", category:"anal", file:"Images/cards/anal/anal_11.webp" }, { id:"anal_12", name:"Anal #12", rarity:"common", category:"anal", file:"Images/cards/anal/anal_12.webp" }, { id:"anal_13", name:"Anal #13", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_13.webp" }, { id:"anal_14", name:"Anal #14", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_14.webp" }, { id:"anal_15", name:"Anal #15", rarity:"epic", category:"anal", file:"Images/cards/anal/anal_15.webp" }, { id:"anal_16", name:"Anal #16", rarity:"epic", category:"anal", file:"Images/cards/anal/anal_16.webp" }, { id:"anal_17", name:"Anal #17", rarity:"common", category:"anal", file:"Images/cards/anal/anal_17.webp" }, { id:"anal_18", name:"Anal #18", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_18.webp" }, { id:"anal_19", name:"Anal #19", rarity:"common", category:"anal", file:"Images/cards/anal/anal_19.webp" }, { id:"anal_20", name:"Anal #20", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_20.webp" }, { id:"anal_21", name:"Anal #21", rarity:"epic", category:"anal", file:"Images/cards/anal/anal_21.webp" }, { id:"anal_22", name:"Anal #22", rarity:"common", category:"anal", file:"Images/cards/anal/anal_22.webp" }, { id:"anal_23", name:"Anal #23", rarity:"common", category:"anal", file:"Images/cards/anal/anal_23.webp" }, { id:"anal_24", name:"Anal #24", rarity:"common", category:"anal", file:"Images/cards/anal/anal_24.webp" }, { id:"anal_25", name:"Anal #25", rarity:"common", category:"anal", file:"Images/cards/anal/anal_25.webp" }, { id:"anal_26", name:"Anal #26", rarity:"epic", category:"anal", file:"Images/cards/anal/anal_26.webp" }, { id:"anal_27", name:"Anal #27", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_27.webp" }, { id:"anal_28", name:"Anal #28", rarity:"common", category:"anal", file:"Images/cards/anal/anal_28.webp" }, { id:"anal_29", name:"Anal #29", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_29.webp" }, { id:"anal_30", name:"Anal #30", rarity:"common", category:"anal", file:"Images/cards/anal/anal_30.webp" }, { id:"anal_31", name:"Anal #31", rarity:"common", category:"anal", file:"Images/cards/anal/anal_31.webp" }, { id:"anal_32", name:"Anal #32", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_32.webp" }, { id:"anal_33", name:"Anal #33", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_33.webp" }, { id:"anal_34", name:"Anal #34", rarity:"rare", category:"anal", file:"Images/cards/anal/anal_34.webp" }, { id:"anal_35", name:"Anal #35", rarity:"common", category:"anal", file:"Images/cards/anal/anal_35.webp" }, { id:"anal_36", name:"Anal #36", rarity:"common", category:"anal", file:"Images/cards/anal/anal_36.webp" }, { id:"anal_37", name:"Anal #37", rarity:"common", category:"anal", file:"Images/cards/anal/anal_37.webp" }, { id:"anal_38", name:"Anal #38", rarity:"common", category:"anal", file:"Images/cards/anal/anal_38.webp" }, { id:"anal_39", name:"Anal #39", rarity:"common", category:"anal", file:"Images/cards/anal/anal_39.webp" }, { id:"anal_40", name:"Anal #40", rarity:"common", category:"anal", file:"Images/cards/anal/anal_40.webp" }, { id:"anal_41", name:"Anal #41", rarity:"common", category:"anal", file:"Images/cards/anal/anal_41.webp" }, { id:"anal_42", name:"Anal #42", rarity:"common", category:"anal", file:"Images/cards/anal/anal_42.webp" }, { id:"anal_43", name:"Anal #43", rarity:"common", category:"anal", file:"Images/cards/anal/anal_43.webp" }, { id:"anal_44", name:"Anal #44", rarity:"common", category:"anal", file:"Images/cards/anal/anal_44.webp" }, { id:"anal_45", name:"Anal #45", rarity:"common", category:"anal", file:"Images/cards/anal/anal_45.webp" }, /* ── PUSSY EATING ── */ { id:"pussy_eating_1", name:"Pussy Eating #1", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_1.webp" }, { id:"pussy_eating_2", name:"Pussy Eating #2", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_2.webp" }, { id:"pussy_eating_3", name:"Pussy Eating #3", rarity:"legendary", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_3.webp" }, { id:"pussy_eating_4", name:"Pussy Eating #4", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_4.webp" }, { id:"pussy_eating_5", name:"Pussy Eating #5", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_5.webp" }, { id:"pussy_eating_6", name:"Pussy Eating #6", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_6.webp" }, { id:"pussy_eating_7", name:"Pussy Eating #7", rarity:"epic", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_7.webp" }, { id:"pussy_eating_8", name:"Pussy Eating #8", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_8.webp" }, { id:"pussy_eating_9", name:"Pussy Eating #9", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_9.webp" }, { id:"pussy_eating_10", name:"Pussy Eating #10", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_10.webp" }, { id:"pussy_eating_11", name:"Pussy Eating #11", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_11.webp" }, { id:"pussy_eating_12", name:"Pussy Eating #12", rarity:"epic", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_12.webp" }, { id:"pussy_eating_13", name:"Pussy Eating #13", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_13.webp" }, { id:"pussy_eating_14", name:"Pussy Eating #14", rarity:"rare", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_14.webp" }, { id:"pussy_eating_15", name:"Pussy Eating #15", rarity:"rare", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_15.webp" }, { id:"pussy_eating_16", name:"Pussy Eating #16", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_16.webp" }, { id:"pussy_eating_17", name:"Pussy Eating #17", rarity:"rare", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_17.webp" }, { id:"pussy_eating_18", name:"Pussy Eating #18", rarity:"rare", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_18.webp" }, { id:"pussy_eating_19", name:"Pussy Eating #19", rarity:"rare", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_19.webp" }, { id:"pussy_eating_20", name:"Pussy Eating #20", rarity:"rare", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_20.webp" }, { id:"pussy_eating_21", name:"Pussy Eating #21", rarity:"epic", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_21.webp" }, { id:"pussy_eating_22", name:"Pussy Eating #22", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_22.webp" }, { id:"pussy_eating_23", name:"Pussy Eating #23", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_23.webp" }, { id:"pussy_eating_24", name:"Pussy Eating #24", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_24.webp" }, { id:"pussy_eating_25", name:"Pussy Eating #25", rarity:"common", category:"pussy_eating", file:"Images/cards/pussy_eating/pussy_eating_25.webp" }, /* ── HANDJOB ── */ { id:"handjob_1", name:"Handjob #1", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_1.webp" }, { id:"handjob_2", name:"Handjob #2", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_2.webp" }, { id:"handjob_3", name:"Handjob #3", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_3.webp" }, { id:"handjob_4", name:"Handjob #4", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_4.webp" }, { id:"handjob_5", name:"Handjob #5", rarity:"rare", category:"handjob", file:"Images/cards/handjob/handjob_5.webp" }, { id:"handjob_6", name:"Handjob #6", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_6.webp" }, { id:"handjob_7", name:"Handjob #7", rarity:"legendary", category:"handjob", file:"Images/cards/handjob/handjob_7.webp" }, { id:"handjob_8", name:"Handjob #8", rarity:"rare", category:"handjob", file:"Images/cards/handjob/handjob_8.webp" }, { id:"handjob_9", name:"Handjob #9", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_9.webp" }, { id:"handjob_10", name:"Handjob #10", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_10.webp" }, { id:"handjob_11", name:"Handjob #11", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_11.webp" }, { id:"handjob_12", name:"Handjob #12", rarity:"rare", category:"handjob", file:"Images/cards/handjob/handjob_12.webp" }, { id:"handjob_13", name:"Handjob #13", rarity:"rare", category:"handjob", file:"Images/cards/handjob/handjob_13.webp" }, { id:"handjob_14", name:"Handjob #14", rarity:"rare", category:"handjob", file:"Images/cards/handjob/handjob_14.webp" }, { id:"handjob_15", name:"Handjob #15", rarity:"epic", category:"handjob", file:"Images/cards/handjob/handjob_15.webp" }, { id:"handjob_16", name:"Handjob #16", rarity:"epic", category:"handjob", file:"Images/cards/handjob/handjob_16.webp" }, { id:"handjob_17", name:"Handjob #17", rarity:"rare", category:"handjob", file:"Images/cards/handjob/handjob_17.webp" }, { id:"handjob_18", name:"Handjob #18", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_18.webp" }, { id:"handjob_19", name:"Handjob #19", rarity:"epic", category:"handjob", file:"Images/cards/handjob/handjob_19.webp" }, { id:"handjob_20", name:"Handjob #20", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_20.webp" }, { id:"handjob_21", name:"Handjob #21", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_21.webp" }, { id:"handjob_22", name:"Handjob #22", rarity:"common", category:"handjob", file:"Images/cards/handjob/handjob_22.webp" }, /* ── FINGERING (18 cartes) ── */ { id:"fingering_1", name:"Fingering #1", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_1.webp" }, { id:"fingering_2", name:"Fingering #2", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_2.webp" }, { id:"fingering_3", name:"Fingering #3", rarity:"rare", category:"fingering", file:"Images/cards/fingering/fingering_3.webp" }, { id:"fingering_4", name:"Fingering #4", rarity:"legendary", category:"fingering", file:"Images/cards/fingering/fingering_4.webp" }, { id:"fingering_5", name:"Fingering #5", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_5.webp" }, { id:"fingering_6", name:"Fingering #6", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_6.webp" }, { id:"fingering_7", name:"Fingering #7", rarity:"rare", category:"fingering", file:"Images/cards/fingering/fingering_7.webp" }, { id:"fingering_8", name:"Fingering #8", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_8.webp" }, { id:"fingering_9", name:"Fingering #9", rarity:"rare", category:"fingering", file:"Images/cards/fingering/fingering_9.webp" }, { id:"fingering_10", name:"Fingering #10", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_10.webp" }, { id:"fingering_11", name:"Fingering #11", rarity:"epic", category:"fingering", file:"Images/cards/fingering/fingering_11.webp" }, { id:"fingering_12", name:"Fingering #12", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_12.webp" }, { id:"fingering_13", name:"Fingering #13", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_13.webp" }, { id:"fingering_14", name:"Fingering #14", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_14.webp" }, { id:"fingering_15", name:"Fingering #15", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_15.webp" }, { id:"fingering_16", name:"Fingering #16", rarity:"rare", category:"fingering", file:"Images/cards/fingering/fingering_16.webp" }, { id:"fingering_17", name:"Fingering #17", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_17.webp" }, { id:"fingering_18", name:"Fingering #18", rarity:"epic", category:"fingering", file:"Images/cards/fingering/fingering_18.webp" }, { id:"fingering_19", name:"Fingering #19", rarity:"epic", category:"fingering", file:"Images/cards/fingering/fingering_19.webp" }, { id:"fingering_20", name:"Fingering #20", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_20.webp" }, { id:"fingering_21", name:"Fingering #21", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_21.webp" }, { id:"fingering_22", name:"Fingering #22", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_22.webp" }, { id:"fingering_23", name:"Fingering #23", rarity:"rare", category:"fingering", file:"Images/cards/fingering/fingering_23.webp" }, { id:"fingering_24", name:"Fingering #24", rarity:"common", category:"fingering", file:"Images/cards/fingering/fingering_24.webp" }, { id:"fingering_25", name:"Fingering #25", rarity:"rare", category:"fingering", file:"Images/cards/fingering/fingering_25.webp" }, /* ── HANDJOB CUM (16 cartes) ── */ { id:"handjob_cum_1", name:"Handjob Cum #1", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_1.webp" }, { id:"handjob_cum_2", name:"Handjob Cum #2", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_2.webp" }, { id:"handjob_cum_3", name:"Handjob Cum #3", rarity:"rare", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_3.webp" }, { id:"handjob_cum_4", name:"Handjob Cum #4", rarity:"rare", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_4.webp" }, { id:"handjob_cum_5", name:"Handjob Cum #5", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_5.webp" }, { id:"handjob_cum_6", name:"Handjob Cum #6", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_6.webp" }, { id:"handjob_cum_7", name:"Handjob Cum #7", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_7.webp" }, { id:"handjob_cum_8", name:"Handjob Cum #8", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_8.webp" }, { id:"handjob_cum_9", name:"Handjob Cum #9", rarity:"rare", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_9.webp" }, { id:"handjob_cum_10", name:"Handjob Cum #10", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_10.webp" }, { id:"handjob_cum_11", name:"Handjob Cum #11", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_11.webp" }, { id:"handjob_cum_12", name:"Handjob Cum #12", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_12.webp" }, { id:"handjob_cum_13", name:"Handjob Cum #13", rarity:"epic", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_13.webp" }, { id:"handjob_cum_14", name:"Handjob Cum #14", rarity:"rare", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_14.webp" }, { id:"handjob_cum_15", name:"Handjob Cum #15", rarity:"epic", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_15.webp" }, { id:"handjob_cum_16", name:"Handjob Cum #16", rarity:"legendary", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_16.webp" }, { id:"handjob_cum_17", name:"Handjob Cum #17", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_17.webp" }, { id:"handjob_cum_18", name:"Handjob Cum #18", rarity:"rare", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_18.webp" }, { id:"handjob_cum_19", name:"Handjob Cum #19", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_19.webp" }, { id:"handjob_cum_20", name:"Handjob Cum #20", rarity:"common", category:"handjob_cum", file:"Images/cards/handjob_cum/handjob_cum_20.webp" }, /* ── TITS FUCK (15 cartes) ── */ { id:"tits_fuck_1", name:"Tits Fuck #1", rarity:"epic", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_1.webp" }, { id:"tits_fuck_2", name:"Tits Fuck #2", rarity:"legendary", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_2.webp" }, { id:"tits_fuck_3", name:"Tits Fuck #3", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_3.webp" }, { id:"tits_fuck_4", name:"Tits Fuck #4", rarity:"rare", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_4.webp" }, { id:"tits_fuck_5", name:"Tits Fuck #5", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_5.webp" }, { id:"tits_fuck_6", name:"Tits Fuck #6", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_6.webp" }, { id:"tits_fuck_7", name:"Tits Fuck #7", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_7.webp" }, { id:"tits_fuck_8", name:"Tits Fuck #8", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_8.webp" }, { id:"tits_fuck_9", name:"Tits Fuck #9", rarity:"rare", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_9.webp" }, { id:"tits_fuck_10", name:"Tits Fuck #10", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_10.webp" }, { id:"tits_fuck_11", name:"Tits Fuck #11", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_11.webp" }, { id:"tits_fuck_12", name:"Tits Fuck #12", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_12.webp" }, { id:"tits_fuck_13", name:"Tits Fuck #13", rarity:"rare", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_13.webp" }, { id:"tits_fuck_14", name:"Tits Fuck #14", rarity:"epic", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_14.webp" }, { id:"tits_fuck_15", name:"Tits Fuck #15", rarity:"rare", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_15.webp" }, { id:"tits_fuck_16", name:"Tits Fuck #16", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_16.webp" }, { id:"tits_fuck_17", name:"Tits Fuck #17", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_17.webp" }, { id:"tits_fuck_18", name:"Tits Fuck #18", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_18.webp" }, { id:"tits_fuck_19", name:"Tits Fuck #19", rarity:"rare", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_19.webp" }, { id:"tits_fuck_20", name:"Tits Fuck #20", rarity:"common", category:"tits_fuck", file:"Images/cards/tits_fuck/tits_fuck_20.webp" }, /* ── BLOWJOB (55 cartes) ── */ { id:"blowjob_1", name:"Blowjob #1", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_1.webp" }, { id:"blowjob_2", name:"Blowjob #2", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_2.webp" }, { id:"blowjob_3", name:"Blowjob #3", rarity:"epic", category:"blowjob", file:"Images/cards/blowjob/blowjob_3.webp" }, { id:"blowjob_4", name:"Blowjob #4", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_4.webp" }, { id:"blowjob_5", name:"Blowjob #5", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_5.webp" }, { id:"blowjob_6", name:"Blowjob #6", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_6.webp" }, { id:"blowjob_7", name:"Blowjob #7", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_7.webp" }, { id:"blowjob_8", name:"Blowjob #8", rarity:"epic", category:"blowjob", file:"Images/cards/blowjob/blowjob_8.webp" }, { id:"blowjob_9", name:"Blowjob #9", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_9.webp" }, { id:"blowjob_10", name:"Blowjob #10", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_10.webp" }, { id:"blowjob_11", name:"Blowjob #11", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_11.webp" }, { id:"blowjob_12", name:"Blowjob #12", rarity:"epic", category:"blowjob", file:"Images/cards/blowjob/blowjob_12.webp" }, { id:"blowjob_13", name:"Blowjob #13", rarity:"epic", category:"blowjob", file:"Images/cards/blowjob/blowjob_13.webp" }, { id:"blowjob_14", name:"Blowjob #14", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_14.webp" }, { id:"blowjob_15", name:"Blowjob #15", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_15.webp" }, { id:"blowjob_16", name:"Blowjob #16", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_16.webp" }, { id:"blowjob_17", name:"Blowjob #17", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_17.webp" }, { id:"blowjob_18", name:"Blowjob #18", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_18.webp" }, { id:"blowjob_19", name:"Blowjob #19", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_19.webp" }, { id:"blowjob_20", name:"Blowjob #20", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_20.webp" }, { id:"blowjob_21", name:"Blowjob #21", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_21.webp" }, { id:"blowjob_22", name:"Blowjob #22", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_22.webp" }, { id:"blowjob_23", name:"Blowjob #23", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_23.webp" }, { id:"blowjob_24", name:"Blowjob #24", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_24.webp" }, { id:"blowjob_25", name:"Blowjob #25", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_25.webp" }, { id:"blowjob_26", name:"Blowjob #26", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_26.webp" }, { id:"blowjob_27", name:"Blowjob #27", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_27.webp" }, { id:"blowjob_28", name:"Blowjob #28", rarity:"legendary", category:"blowjob", file:"Images/cards/blowjob/blowjob_28.webp" }, { id:"blowjob_29", name:"Blowjob #29", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_29.webp" }, { id:"blowjob_30", name:"Blowjob #30", rarity:"epic", category:"blowjob", file:"Images/cards/blowjob/blowjob_30.webp" }, { id:"blowjob_31", name:"Blowjob #31", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_31.webp" }, { id:"blowjob_32", name:"Blowjob #32", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_32.webp" }, { id:"blowjob_33", name:"Blowjob #33", rarity:"epic", category:"blowjob", file:"Images/cards/blowjob/blowjob_33.webp" }, { id:"blowjob_34", name:"Blowjob #34", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_34.webp" }, { id:"blowjob_35", name:"Blowjob #35", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_35.webp" }, { id:"blowjob_36", name:"Blowjob #36", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_36.webp" }, { id:"blowjob_37", name:"Blowjob #37", rarity:"legendary", category:"blowjob", file:"Images/cards/blowjob/blowjob_37.webp" }, { id:"blowjob_38", name:"Blowjob #38", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_38.webp" }, { id:"blowjob_39", name:"Blowjob #39", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_39.webp" }, { id:"blowjob_40", name:"Blowjob #40", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_40.webp" }, { id:"blowjob_41", name:"Blowjob #41", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_41.webp" }, { id:"blowjob_42", name:"Blowjob #42", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_42.webp" }, { id:"blowjob_43", name:"Blowjob #43", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_43.webp" }, { id:"blowjob_44", name:"Blowjob #44", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_44.webp" }, { id:"blowjob_45", name:"Blowjob #45", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_45.webp" }, { id:"blowjob_46", name:"Blowjob #46", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_46.webp" }, { id:"blowjob_47", name:"Blowjob #47", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_47.webp" }, { id:"blowjob_48", name:"Blowjob #48", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_48.webp" }, { id:"blowjob_49", name:"Blowjob #49", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_49.webp" }, { id:"blowjob_50", name:"Blowjob #50", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_50.webp" }, { id:"blowjob_51", name:"Blowjob #51", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_51.webp" }, { id:"blowjob_52", name:"Blowjob #52", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_52.webp" }, { id:"blowjob_53", name:"Blowjob #53", rarity:"common", category:"blowjob", file:"Images/cards/blowjob/blowjob_53.webp" }, { id:"blowjob_54", name:"Blowjob #54", rarity:"epic", category:"blowjob", file:"Images/cards/blowjob/blowjob_54.webp" }, { id:"blowjob_55", name:"Blowjob #55", rarity:"rare", category:"blowjob", file:"Images/cards/blowjob/blowjob_55.webp" }, /* ── RIMJOB (20 cartes) ── */ { id:"rimjob_1", name:"Rimjob #1", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_1.webp" }, { id:"rimjob_2", name:"Rimjob #2", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_2.webp" }, { id:"rimjob_3", name:"Rimjob #3", rarity:"rare", category:"rimjob", file:"Images/cards/rimjob/rimjob_3.webp" }, { id:"rimjob_4", name:"Rimjob #4", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_4.webp" }, { id:"rimjob_5", name:"Rimjob #5", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_5.webp" }, { id:"rimjob_6", name:"Rimjob #6", rarity:"epic", category:"rimjob", file:"Images/cards/rimjob/rimjob_6.webp" }, { id:"rimjob_7", name:"Rimjob #7", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_7.webp" }, { id:"rimjob_8", name:"Rimjob #8", rarity:"rare", category:"rimjob", file:"Images/cards/rimjob/rimjob_8.webp" }, { id:"rimjob_9", name:"Rimjob #9", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_9.webp" }, { id:"rimjob_10", name:"Rimjob #10", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_10.webp" }, { id:"rimjob_11", name:"Rimjob #11", rarity:"rare", category:"rimjob", file:"Images/cards/rimjob/rimjob_11.webp" }, { id:"rimjob_12", name:"Rimjob #12", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_12.webp" }, { id:"rimjob_13", name:"Rimjob #13", rarity:"epic", category:"rimjob", file:"Images/cards/rimjob/rimjob_13.webp" }, { id:"rimjob_14", name:"Rimjob #14", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_14.webp" }, { id:"rimjob_15", name:"Rimjob #15", rarity:"rare", category:"rimjob", file:"Images/cards/rimjob/rimjob_15.webp" }, { id:"rimjob_16", name:"Rimjob #16", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_16.webp" }, { id:"rimjob_17", name:"Rimjob #17", rarity:"rare", category:"rimjob", file:"Images/cards/rimjob/rimjob_17.webp" }, { id:"rimjob_18", name:"Rimjob #18", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_18.webp" }, { id:"rimjob_19", name:"Rimjob #19", rarity:"common", category:"rimjob", file:"Images/cards/rimjob/rimjob_19.webp" }, { id:"rimjob_20", name:"Rimjob #20", rarity:"legendary", category:"rimjob", file:"Images/cards/rimjob/rimjob_20.webp" }, /* ── THREESOME (30 cartes) ── */ { id:"threesome_1", name:"Threesome #1", rarity:"rare", category:"threesome", file:"Images/cards/threesome/threesome_1.webp" }, { id:"threesome_2", name:"Threesome #2", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_2.webp" }, { id:"threesome_3", name:"Threesome #3", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_3.webp" }, { id:"threesome_4", name:"Threesome #4", rarity:"epic", category:"threesome", file:"Images/cards/threesome/threesome_4.webp" }, { id:"threesome_5", name:"Threesome #5", rarity:"rare", category:"threesome", file:"Images/cards/threesome/threesome_5.webp" }, { id:"threesome_6", name:"Threesome #6", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_6.webp" }, { id:"threesome_7", name:"Threesome #7", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_7.webp" }, { id:"threesome_8", name:"Threesome #8", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_8.webp" }, { id:"threesome_9", name:"Threesome #9", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_9.webp" }, { id:"threesome_10", name:"Threesome #10", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_10.webp" }, { id:"threesome_11", name:"Threesome #11", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_11.webp" }, { id:"threesome_12", name:"Threesome #12", rarity:"rare", category:"threesome", file:"Images/cards/threesome/threesome_12.webp" }, { id:"threesome_13", name:"Threesome #13", rarity:"legendary", category:"threesome", file:"Images/cards/threesome/threesome_13.webp" }, { id:"threesome_14", name:"Threesome #14", rarity:"rare", category:"threesome", file:"Images/cards/threesome/threesome_14.webp" }, { id:"threesome_15", name:"Threesome #15", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_15.webp" }, { id:"threesome_16", name:"Threesome #16", rarity:"epic", category:"threesome", file:"Images/cards/threesome/threesome_16.webp" }, { id:"threesome_17", name:"Threesome #17", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_17.webp" }, { id:"threesome_18", name:"Threesome #18", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_18.webp" }, { id:"threesome_19", name:"Threesome #19", rarity:"rare", category:"threesome", file:"Images/cards/threesome/threesome_19.webp" }, { id:"threesome_20", name:"Threesome #20", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_20.webp" }, { id:"threesome_21", name:"Threesome #21", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_21.webp" }, { id:"threesome_22", name:"Threesome #22", rarity:"epic", category:"threesome", file:"Images/cards/threesome/threesome_22.webp" }, { id:"threesome_23", name:"Threesome #23", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_23.webp" }, { id:"threesome_24", name:"Threesome #24", rarity:"rare", category:"threesome", file:"Images/cards/threesome/threesome_24.webp" }, { id:"threesome_25", name:"Threesome #25", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_25.webp" }, { id:"threesome_26", name:"Threesome #26", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_26.webp" }, { id:"threesome_27", name:"Threesome #27", rarity:"rare", category:"threesome", file:"Images/cards/threesome/threesome_27.webp" }, { id:"threesome_28", name:"Threesome #28", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_28.webp" }, { id:"threesome_29", name:"Threesome #29", rarity:"common", category:"threesome", file:"Images/cards/threesome/threesome_29.webp" }, { id:"threesome_30", name:"Threesome #30", rarity:"rare", category:"threesome", file:"Images/cards/threesome/threesome_30.webp" }, /* ── DP (25 cartes) ── */ { id:"dp_1", name:"DP #1", rarity:"common", category:"dp", file:"Images/cards/dp/dp_1.webp" }, { id:"dp_2", name:"DP #2", rarity:"rare", category:"dp", file:"Images/cards/dp/dp_2.webp" }, { id:"dp_3", name:"DP #3", rarity:"common", category:"dp", file:"Images/cards/dp/dp_3.webp" }, { id:"dp_4", name:"DP #4", rarity:"epic", category:"dp", file:"Images/cards/dp/dp_4.webp" }, { id:"dp_5", name:"DP #5", rarity:"epic", category:"dp", file:"Images/cards/dp/dp_5.webp" }, { id:"dp_6", name:"DP #6", rarity:"rare", category:"dp", file:"Images/cards/dp/dp_6.webp" }, { id:"dp_7", name:"DP #7", rarity:"common", category:"dp", file:"Images/cards/dp/dp_7.webp" }, { id:"dp_8", name:"DP #8", rarity:"legendary", category:"dp", file:"Images/cards/dp/dp_8.webp" }, { id:"dp_9", name:"DP #9", rarity:"common", category:"dp", file:"Images/cards/dp/dp_9.webp" }, { id:"dp_10", name:"DP #10", rarity:"common", category:"dp", file:"Images/cards/dp/dp_10.webp" }, { id:"dp_11", name:"DP #11", rarity:"rare", category:"dp", file:"Images/cards/dp/dp_11.webp" }, { id:"dp_12", name:"DP #12", rarity:"common", category:"dp", file:"Images/cards/dp/dp_12.webp" }, { id:"dp_13", name:"DP #13", rarity:"rare", category:"dp", file:"Images/cards/dp/dp_13.webp" }, { id:"dp_14", name:"DP #14", rarity:"common", category:"dp", file:"Images/cards/dp/dp_14.webp" }, { id:"dp_15", name:"DP #15", rarity:"common", category:"dp", file:"Images/cards/dp/dp_15.webp" }, { id:"dp_16", name:"DP #16", rarity:"epic", category:"dp", file:"Images/cards/dp/dp_16.webp" }, { id:"dp_17", name:"DP #17", rarity:"common", category:"dp", file:"Images/cards/dp/dp_17.webp" }, { id:"dp_18", name:"DP #18", rarity:"common", category:"dp", file:"Images/cards/dp/dp_18.webp" }, { id:"dp_19", name:"DP #19", rarity:"rare", category:"dp", file:"Images/cards/dp/dp_19.webp" }, { id:"dp_20", name:"DP #20", rarity:"common", category:"dp", file:"Images/cards/dp/dp_20.webp" }, { id:"dp_21", name:"DP #21", rarity:"common", category:"dp", file:"Images/cards/dp/dp_21.webp" }, { id:"dp_22", name:"DP #22", rarity:"rare", category:"dp", file:"Images/cards/dp/dp_22.webp" }, { id:"dp_23", name:"DP #23", rarity:"common", category:"dp", file:"Images/cards/dp/dp_23.webp" }, { id:"dp_24", name:"DP #24", rarity:"common", category:"dp", file:"Images/cards/dp/dp_24.webp" }, { id:"dp_25", name:"DP #25", rarity:"common", category:"dp", file:"Images/cards/dp/dp_25.webp" }, /* ── BBC (30 cartes) ── */ { id:"bbc_1", name:"BBC #1", rarity:"rare", category:"bbc", file:"Images/cards/bbc/bbc_1.webp" }, { id:"bbc_2", name:"BBC #2", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_2.webp" }, { id:"bbc_3", name:"BBC #3", rarity:"rare", category:"bbc", file:"Images/cards/bbc/bbc_3.webp" }, { id:"bbc_4", name:"BBC #4", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_4.webp" }, { id:"bbc_5", name:"BBC #5", rarity:"legendary", category:"bbc", file:"Images/cards/bbc/bbc_5.webp" }, { id:"bbc_6", name:"BBC #6", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_6.webp" }, { id:"bbc_7", name:"BBC #7", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_7.webp" }, { id:"bbc_8", name:"BBC #8", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_8.webp" }, { id:"bbc_9", name:"BBC #9", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_9.webp" }, { id:"bbc_10", name:"BBC #10", rarity:"epic", category:"bbc", file:"Images/cards/bbc/bbc_10.webp" }, { id:"bbc_11", name:"BBC #11", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_11.webp" }, { id:"bbc_12", name:"BBC #12", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_12.webp" }, { id:"bbc_13", name:"BBC #13", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_13.webp" }, { id:"bbc_14", name:"BBC #14", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_14.webp" }, { id:"bbc_15", name:"BBC #15", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_15.webp" }, { id:"bbc_16", name:"BBC #16", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_16.webp" }, { id:"bbc_17", name:"BBC #17", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_17.webp" }, { id:"bbc_18", name:"BBC #18", rarity:"rare", category:"bbc", file:"Images/cards/bbc/bbc_18.webp" }, { id:"bbc_19", name:"BBC #19", rarity:"rare", category:"bbc", file:"Images/cards/bbc/bbc_19.webp" }, { id:"bbc_20", name:"BBC #20", rarity:"rare", category:"bbc", file:"Images/cards/bbc/bbc_20.webp" }, { id:"bbc_21", name:"BBC #21", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_21.webp" }, { id:"bbc_22", name:"BBC #22", rarity:"epic", category:"bbc", file:"Images/cards/bbc/bbc_22.webp" }, { id:"bbc_23", name:"BBC #23", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_23.webp" }, { id:"bbc_24", name:"BBC #24", rarity:"rare", category:"bbc", file:"Images/cards/bbc/bbc_24.webp" }, { id:"bbc_25", name:"BBC #25", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_25.webp" }, { id:"bbc_26", name:"BBC #26", rarity:"rare", category:"bbc", file:"Images/cards/bbc/bbc_26.webp" }, { id:"bbc_27", name:"BBC #27", rarity:"epic", category:"bbc", file:"Images/cards/bbc/bbc_27.webp" }, { id:"bbc_28", name:"BBC #28", rarity:"rare", category:"bbc", file:"Images/cards/bbc/bbc_28.webp" }, { id:"bbc_29", name:"BBC #29", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_29.webp" }, { id:"bbc_30", name:"BBC #30", rarity:"common", category:"bbc", file:"Images/cards/bbc/bbc_30.webp" }, /* ── TS TEASE (30 cartes) ── */ { id:"ts_tease_1", name:"TS Tease #1", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_1.webp" }, { id:"ts_tease_2", name:"TS Tease #2", rarity:"rare", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_2.webp" }, { id:"ts_tease_3", name:"TS Tease #3", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_3.webp" }, { id:"ts_tease_4", name:"TS Tease #4", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_4.webp" }, { id:"ts_tease_5", name:"TS Tease #5", rarity:"epic", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_5.webp" }, { id:"ts_tease_6", name:"TS Tease #6", rarity:"epic", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_6.webp" }, { id:"ts_tease_7", name:"TS Tease #7", rarity:"rare", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_7.webp" }, { id:"ts_tease_8", name:"TS Tease #8", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_8.webp" }, { id:"ts_tease_9", name:"TS Tease #9", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_9.webp" }, { id:"ts_tease_10", name:"TS Tease #10", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_10.webp" }, { id:"ts_tease_11", name:"TS Tease #11", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_11.webp" }, { id:"ts_tease_12", name:"TS Tease #12", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_12.webp" }, { id:"ts_tease_13", name:"TS Tease #13", rarity:"rare", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_13.webp" }, { id:"ts_tease_14", name:"TS Tease #14", rarity:"legendary", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_14.webp" }, { id:"ts_tease_15", name:"TS Tease #15", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_15.webp" }, { id:"ts_tease_16", name:"TS Tease #16", rarity:"rare", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_16.webp" }, { id:"ts_tease_17", name:"TS Tease #17", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_17.webp" }, { id:"ts_tease_18", name:"TS Tease #18", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_18.webp" }, { id:"ts_tease_19", name:"TS Tease #19", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_19.webp" }, { id:"ts_tease_20", name:"TS Tease #20", rarity:"rare", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_20.webp" }, { id:"ts_tease_21", name:"TS Tease #21", rarity:"epic", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_21.webp" }, { id:"ts_tease_22", name:"TS Tease #22", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_22.webp" }, { id:"ts_tease_23", name:"TS Tease #23", rarity:"rare", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_23.webp" }, { id:"ts_tease_24", name:"TS Tease #24", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_24.webp" }, { id:"ts_tease_25", name:"TS Tease #25", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_25.webp" }, { id:"ts_tease_26", name:"TS Tease #26", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_26.webp" }, { id:"ts_tease_27", name:"TS Tease #27", rarity:"rare", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_27.webp" }, { id:"ts_tease_28", name:"TS Tease #28", rarity:"common", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_28.webp" }, { id:"ts_tease_29", name:"TS Tease #29", rarity:"epic", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_29.webp" }, { id:"ts_tease_30", name:"TS Tease #30", rarity:"rare", category:"ts_tease", file:"Images/cards/trans/ts_tease/ts_tease_30.webp" }, /* ── TS SOLO (23 cartes) ── */ { id:"ts_solo_1", name:"TS Solo #1", rarity:"rare", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_1.webp" }, { id:"ts_solo_2", name:"TS Solo #2", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_2.webp" }, { id:"ts_solo_3", name:"TS Solo #3", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_3.webp" }, { id:"ts_solo_4", name:"TS Solo #4", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_4.webp" }, { id:"ts_solo_5", name:"TS Solo #5", rarity:"rare", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_5.webp" }, { id:"ts_solo_6", name:"TS Solo #6", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_6.webp" }, { id:"ts_solo_7", name:"TS Solo #7", rarity:"rare", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_7.webp" }, { id:"ts_solo_8", name:"TS Solo #8", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_8.webp" }, { id:"ts_solo_9", name:"TS Solo #9", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_9.webp" }, { id:"ts_solo_10", name:"TS Solo #10", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_10.webp" }, { id:"ts_solo_11", name:"TS Solo #11", rarity:"epic", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_11.webp" }, { id:"ts_solo_12", name:"TS Solo #12", rarity:"rare", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_12.webp" }, { id:"ts_solo_13", name:"TS Solo #13", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_13.webp" }, { id:"ts_solo_14", name:"TS Solo #14", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_14.webp" }, { id:"ts_solo_15", name:"TS Solo #15", rarity:"legendary", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_15.webp" }, { id:"ts_solo_16", name:"TS Solo #16", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_16.webp" }, { id:"ts_solo_17", name:"TS Solo #17", rarity:"rare", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_17.webp" }, { id:"ts_solo_18", name:"TS Solo #18", rarity:"rare", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_18.webp" }, { id:"ts_solo_19", name:"TS Solo #19", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_19.webp" }, { id:"ts_solo_20", name:"TS Solo #20", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_20.webp" }, { id:"ts_solo_21", name:"TS Solo #21", rarity:"epic", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_21.webp" }, { id:"ts_solo_22", name:"TS Solo #22", rarity:"epic", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_22.webp" }, { id:"ts_solo_23", name:"TS Solo #23", rarity:"common", category:"ts_solo", file:"Images/cards/trans/ts_solo/ts_solo_23.webp" }, /* ── TS HANDJOB (12 cartes) ── */ { id:"ts_handjob_1", name:"TS Handjob #1", rarity:"legendary", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_1.webp" }, { id:"ts_handjob_2", name:"TS Handjob #2", rarity:"common", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_2.webp" }, { id:"ts_handjob_3", name:"TS Handjob #3", rarity:"common", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_3.webp" }, { id:"ts_handjob_4", name:"TS Handjob #4", rarity:"common", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_4.webp" }, { id:"ts_handjob_5", name:"TS Handjob #5", rarity:"common", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_5.webp" }, { id:"ts_handjob_6", name:"TS Handjob #6", rarity:"common", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_6.webp" }, { id:"ts_handjob_7", name:"TS Handjob #7", rarity:"common", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_7.webp" }, { id:"ts_handjob_8", name:"TS Handjob #8", rarity:"rare", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_8.webp" }, { id:"ts_handjob_9", name:"TS Handjob #9", rarity:"rare", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_9.webp" }, { id:"ts_handjob_10", name:"TS Handjob #10", rarity:"rare", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_10.webp" }, { id:"ts_handjob_11", name:"TS Handjob #11", rarity:"epic", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_11.webp" }, { id:"ts_handjob_12", name:"TS Handjob #12", rarity:"common", category:"ts_handjob", file:"Images/cards/trans/ts_handjob/ts_handjob_12.webp" }, ]; /* Build public : les cartes du monde Trans n'existent pas (aucun total, aucune collection, aucun pack ne les compte). */ if (!window.TRANS_ENABLED) { window.CARD_DB = window.CARD_DB.filter(function(c) { return !(window.CATEGORIES || []).some(function(cat) { return cat.tag === c.category && cat.branch === 'trans'; }); }); } /* Poids de probabilité par rareté */ window.RARITY_WEIGHTS = { common:60, rare:25, epic:12, legendary:3 }; /* Tirer une rareté pondérée */ window.rollRarity = function(forcedRarity) { if (forcedRarity) return forcedRarity; var total = 0; for (var k in window.RARITY_WEIGHTS) total += window.RARITY_WEIGHTS[k]; var roll = Math.random() * total; for (var rarity in window.RARITY_WEIGHTS) { roll -= window.RARITY_WEIGHTS[rarity]; if (roll <= 0) return rarity; } return "common"; }; /* Tirer une carte pour un pack */ window.drawPackCard = function(packId) { var pack = window.PACKS.find(function(p) { return p.id === packId; }); if (!pack) return null; var rarity = window.rollRarity(pack.forcedRarity || null); var pool = window.CARD_DB.filter(function(c) { return pack.categories.indexOf(c.category) !== -1 && c.rarity === rarity; }); if (!pool.length) { pool = window.CARD_DB.filter(function(c) { return pack.categories.indexOf(c.category) !== -1; }); } if (!pool.length) return null; return pool[Math.floor(Math.random() * pool.length)]; }; /* Taille et multiplicateur de prix des packs XXL */ window.PACK_XXL_CARDS = 9; window.PACK_XXL_MULT = 3; /* Prix effectif d'un pack selon sa taille */ window.packPrice = function(pack, xxl) { if (!pack) return 0; return xxl ? pack.price * window.PACK_XXL_MULT : pack.price; }; /* Ouvrir un pack — 3 cartes (classique) ou 9 cartes (XXL, 3× le prix) */ window.openPack = function(packId, free, xxl) { var v = State.variables; var pack = window.PACKS.find(function(p) { return p.id === packId; }); if (!pack) return false; var cardCount = xxl ? window.PACK_XXL_CARDS : 3; var cost = window.packPrice(pack, xxl); /* Initialiser tokens si absent (sauvegarde ancienne) */ if (!v.tokens) v.tokens = { vice:0, boudoir:0, heat:0, slutpit:0, heterovip:0, rainbow:0, gayvip:0, transroom:0, velvet:0, dollhouse:0, herturn:0, trance:0, transvip:0, penthouse:0 }; if (!free) { /* Vérifier solde de jetons pour cette table */ var tableTokens = v.tokens[pack.table] || 0; if (tableTokens < cost) return false; /* Vérifier table débloquée */ var tableOk = ( v.unlockedTables.indexOf(pack.table) !== -1 || v.unlockedTablesGay.indexOf(pack.table) !== -1 || v.unlockedTablesTrans.indexOf(pack.table) !== -1 || pack.table === 'vice' || pack.table === 'rainbow' || pack.table === 'transroom' ); if (!tableOk) return false; } /* Vérifier qu'il y a des cartes disponibles */ var hasCards = window.CARD_DB.some(function(c) { return pack.categories.indexOf(c.category) !== -1; }); if (!hasCards) return 'empty'; /* Déduire les jetons sauf en mode gratuit */ if (!free) v.tokens[pack.table] -= cost; v.currentPackId = packId; v.currentPackXXL = !!xxl; v.pendingPack = []; for (var i = 0; i < cardCount; i++) { var card = window.drawPackCard(packId); if (card) { v.pendingPack.push(card.id); if (v.collection[card.id] === undefined) v.collection[card.id] = 0; v.collection[card.id]++; } } return true; }; /* Récupérer les jetons d'une table */ window.getTableTokens = function(tableId) { var v = State.variables; if (!v.tokens) return 0; return v.tokens[tableId] || 0; }; /* Formatter les jetons */ window.formatTokens = function(n) { return n + ' 🎫'; }; /* Récupérer une carte par id */ window.getCard = function(cardId) { return window.CARD_DB.find(function(c) { return c.id === cardId; }) || null; }; /* Nombre de cartes uniques possédées */ /* Nombre de cartes possedees DANS LA BRANCHE ACTIVE. Les mondes etant cloisonnes, le compteur de la sidebar ne doit pas additionner les cartes des autres mondes. */ window.collectionCount = function() { var v = State.variables; var col = v.collection || {}; var br = v.activeBranch || 'hetero'; var tags = (window.CATEGORIES || []) .filter(function(c) { return c.branch === br; }) .map(function(c) { return c.tag; }); return (window.CARD_DB || []).filter(function(c) { return tags.indexOf(c.category) !== -1 && (col[c.id] || 0) > 0; }).length; }; /* Icône rareté */ window.rarityIcon = function(r) { return { common:"⚪", rare:"🔵", epic:"🟣", legendary:"🌟" }[r] || ""; }; /* Label lisible d'une catégorie */ window.categoryLabel = function(tag) { var cat = window.CATEGORIES.find(function(c) { return c.tag === tag; }); return cat ? cat.label : tag; }; /* Tables accessibles selon l'état du joueur */ window.isTableUnlocked = function(tableId) { var v = State.variables; var tbl = window.TABLES.find(function(t) { return t.id === tableId; }); if (!tbl) return false; if (tbl.branch === "hetero") return v.unlockedTables.indexOf(tableId) !== -1; if (tbl.branch === "gay") return v.gayUnlocked && v.unlockedTablesGay.indexOf(tableId) !== -1; if (tbl.branch === "trans") return v.transUnlocked && v.unlockedTablesTrans.indexOf(tableId) !== -1; if (tbl.branch === "both") return v.unlockedTables.indexOf("heterovip") !== -1 && v.gayUnlocked && v.unlockedTablesGay.indexOf("gayvip") !== -1; return false; }; <</script>> /* ══════════════════════════════════════════ VARIABLES DU JEU ══════════════════════════════════════════ */ <<set $playerMoney = 1500>> /* Chaque monde a sa propre bourse. $playerMoney porte celle du monde actif ; les deux autres sont mises de cote ici et echangees au changement de monde. */ <<set $branchMoney = { hetero:1500, gay:1500, trans:1500 }>> /* Essence de transmutation, cloisonnee par monde comme le reste */ <<set $essence = { hetero:0, gay:0, trans:0 }>> <<set $currentBet = 0>> <<set $baseBet = 0>> <<set $gameState = "betting">> <<set $playerHand = []>> <<set $dealerHand = []>> <<set $canDouble = false>> <<set $canSplit = false>> <<set $isSplit = false>> <<set $splitHand = []>> <<set $splitBet = 0>> <<set $currentHand = "main">> /* Stats */ <<set $totalWins = 0>> <<set $totalLosses = 0>> <<set $totalPushes = 0>> <<set $totalHandsPlayed = 0>> <<set $biggestWin = 0>> <<set $earnedHetero = 0>> <<set $earnedGay = 0>> <<set $earnedTrans = 0>> <<set $totalEarned = 0>> /* ══════════════════════════════════════════ COLLECTION SYSTEM ══════════════════════════════════════════ */ <<set $collection = {}>> /* Branche hétéro */ <<set $unlockedTables = ["vice"]>> /* Branche gay */ <<set $gayUnlocked = true>> <<set $unlockedTablesGay = ["rainbow"]>> /* Branche trans */ <<set $transUnlocked = true>> <<set $unlockedTablesTrans = ["transroom"]>> /* Table active */ <<set $activeTable = "vice">> <<set $activeBranch = "hetero">> /* Pack en cours */ <<set $pendingPack = []>> <<set $currentPackId = "vice_pack">> <<set $currentPackXXL = false>> /* ══════════════════════════════════════════ JETONS PAR TABLE — monnaie de progression Gagnés en jouant, dépensés pour les packs ══════════════════════════════════════════ */ <<set $tokens = { vice:0, boudoir:0, heat:0, slutpit:0, heterovip:0, rainbow:0, gayvip:0, transroom:0, transvip:0, penthouse:0 }>> <<set $lastResultBlackjack = false>> <<set $lastHandWinMoney = 0>> <<set $lastHandCanDouble = false>> /* Lucky Wheel */ <<set $wheelHandsCount = { hetero:0, gay:0, trans:0 }>> <<set $wheelReady = { hetero:false, gay:false, trans:false }>> /* Pages vues (pour les bulles first-visit) */ <<set $visitedPages = {}>> /* Game Over count */ <<set $gameOverCount = { hetero:0, gay:0, trans:0 }>> /* NPC — session flags */ <<set $scarlettSeenThisSession = false>> <<set $transHostSeenThisSession = false>> <<set $firstWinSeen = false>> /* Table unlock tips */ <<set $tableUnlockSeen = { boudoir:false, heat:false, slutpit:false, heterovip:false, penthouse:false }>> /* Streak system */ <<set $currentStreak = { vice:0, boudoir:0, heat:0, slutpit:0, heterovip:0, rainbow:0, gayvip:0, transroom:0, velvet:0, dollhouse:0, herturn:0, trance:0, transvip:0, penthouse:0 }>> <<set $streakMilestoneSeen = { s3:false, s5:false, s7:false, s10:false }>> /* Serie de pertes cachee + lot de consolation */ <<set $lossStreak = {}>> <<set $consolationCooldown = { hetero:0, gay:0, trans:0 }>> <<set $consolationPending = null>> /* ══════════════════════════════════════════ MIGRATION — compatibilité saves anciennes Ajouter ici chaque nouvelle variable intro- duite dans une update, avec sa valeur par défaut. Ne jamais écraser une valeur existante. v0.1 → v0.2 ══════════════════════════════════════════ */ <<script>> /* Fonction nommee : StoryInit ne s'execute QUE sur une partie neuve. Au chargement ou a l'import d'une sauvegarde, il faut la rejouer, sinon les variables introduites apres coup restent absentes. */ window.migrateState = function() { var v = State.variables; /* Build public : une sauvegarde faite dans le monde Trans (build Patreon) est ramenee dans le monde Straight, sans rien perdre. */ if (!window.TRANS_ENABLED && v.activeBranch === 'trans') { if (!v.branchMoney) v.branchMoney = { hetero:1500, gay:1500, trans:1500 }; v.branchMoney.trans = v.playerMoney; v.activeBranch = 'hetero'; v.playerMoney = (typeof v.branchMoney.hetero === 'number') ? v.branchMoney.hetero : 1500; v.activeTable = (v.unlockedTables && v.unlockedTables[v.unlockedTables.length - 1]) || 'vice'; } if (v.scarlettSeenThisSession === undefined) v.scarlettSeenThisSession = false; if (v.transHostSeenThisSession === undefined) v.transHostSeenThisSession = false; if (v.firstWinSeen === undefined) v.firstWinSeen = false; if (v.lastHandResult === undefined) v.lastHandResult = ''; if (v.lastHandTokens === undefined) v.lastHandTokens = 0; if (v.lastHandWinMoney === undefined) v.lastHandWinMoney = 0; if (v.lastHandCanDouble === undefined) v.lastHandCanDouble = false; if (v.heatUnlockedSeen === undefined) v.heatUnlockedSeen = false; if (v.tableUnlockSeen === undefined) v.tableUnlockSeen = { boudoir:false, heat:false, slutpit:false, heterovip:false, penthouse:false }; if (v.currentStreak === undefined || typeof v.currentStreak !== 'object') v.currentStreak = { vice:0, boudoir:0, heat:0, slutpit:0, heterovip:0, rainbow:0, gayvip:0, transroom:0, velvet:0, dollhouse:0, herturn:0, trance:0, transvip:0, penthouse:0 }; if (v.streakMilestoneSeen === undefined) v.streakMilestoneSeen = { s3:false, s5:false, s7:false, s10:false }; if (typeof v.lossStreak !== 'object' || v.lossStreak === null) v.lossStreak = {}; if (typeof v.consolationCooldown !== 'object' || v.consolationCooldown === null) v.consolationCooldown = { hetero:0, gay:0, trans:0 }; if (v.consolationPending === undefined) v.consolationPending = null; /* Bourses separees par monde — migration des saves anterieures : l'ancien solde unique est attribue au monde hetero. */ if (typeof v.branchMoney !== 'object' || v.branchMoney === null) { v.branchMoney = { hetero: (v.playerMoney || 1500), gay: 1500, trans: 1500 }; } ['hetero','gay','trans'].forEach(function(b) { if (typeof v.branchMoney[b] !== 'number') v.branchMoney[b] = 1500; }); /* Essence de transmutation */ if (typeof v.essence !== 'object' || v.essence === null) { v.essence = { hetero:0, gay:0, trans:0 }; } ['hetero','gay','trans'].forEach(function(b) { if (typeof v.essence[b] !== 'number') v.essence[b] = 0; }); /* Compteur de Game Over par monde */ if (typeof v.gameOverCount !== 'object' || v.gameOverCount === null) { v.gameOverCount = { hetero: (v.gameOverCount || 0), gay: 0, trans: 0 }; } ['hetero','gay','trans'].forEach(function(b) { if (typeof v.gameOverCount[b] !== 'number') v.gameOverCount[b] = 0; }); /* Branche trans ouverte des le depart — migration des saves anterieures */ v.transUnlocked = true; if (!v.unlockedTablesTrans || !v.unlockedTablesTrans.length) v.unlockedTablesTrans = ["transroom"]; /* Lucky Wheel : autrefois globale, desormais une par branche. On convertit les anciennes sauvegardes en reportant la progression existante sur la branche hetero. */ if (typeof v.wheelHandsCount !== 'object' || v.wheelHandsCount === null) { v.wheelHandsCount = { hetero: (v.wheelHandsCount || 0), gay: 0, trans: 0 }; } if (typeof v.wheelReady !== 'object' || v.wheelReady === null) { v.wheelReady = { hetero: (v.wheelReady || false), gay: false, trans: false }; } ['hetero','gay','trans'].forEach(function(b) { if (v.wheelHandsCount[b] === undefined) v.wheelHandsCount[b] = 0; if (v.wheelReady[b] === undefined) v.wheelReady[b] = false; }); }; /* Partie neuve */ window.migrateState(); /* Chargement d'un slot ou import d'un fichier .21dsave : SugarCube restaure les variables telles qu'elles etaient, donc on rejoue la migration juste apres. */ if (typeof Save !== 'undefined' && Save.onLoad && typeof Save.onLoad.add === 'function') { Save.onLoad.add(function() { if (window.migrateState) window.migrateState(); }); } <</script>> /* ══════════════════════════════════════════ BROWSER SAVES — 3 slots SugarCube ══════════════════════════════════════════ */ <<run Config.saves.slots = 3;>> <<script>> /* ══════════════════════════════════════════ SIDEBAR DE NAVIGATION PERSISTANTE Présente sur toutes les pages sauf : Warning, Leave, Intro, World Select ══════════════════════════════════════════ */ var NO_SIDEBAR = ['Warning', 'Leave', 'Intro', 'How To Play', 'World Select', 'Welcome Straight']; var GAME_PAGES = ['Play Hand', 'Dealer Turn', 'Game Result']; /* ══════════════════════════════════════════ FIRST-VISIT TIPS ══════════════════════════════════════════ */ /* ══════════════════════════════════════════ DEV PANEL — Ctrl+Shift+D Accessible uniquement au concepteur ══════════════════════════════════════════ */ (function() { var DEV_PANEL_ID = 'dev-cheat-panel'; function buildDevPanel() { if (document.getElementById(DEV_PANEL_ID)) return; var panel = document.createElement('div'); panel.id = DEV_PANEL_ID; panel.innerHTML = '<div id="dev-panel-title">⚙️ DEV PANEL</div>' + '<div class="dev-section-label">💰 Money</div>' + '<div class="dev-row">' + '<button class="dev-btn" data-action="money" data-val="5000">+$5k</button>' + '<button class="dev-btn" data-action="money" data-val="20000">+$20k</button>' + '<button class="dev-btn" data-action="money" data-val="50000">+$50k</button>' + '<button class="dev-btn" data-action="money" data-val="200000">+$200k</button>' + '</div>' + '<div class="dev-section-label">🎫 Tokens (table active)</div>' + '<div class="dev-row">' + '<button class="dev-btn" data-action="tokens" data-val="100">+100</button>' + '<button class="dev-btn" data-action="tokens" data-val="500">+500</button>' + '<button class="dev-btn" data-action="tokens" data-val="2000">+2000</button>' + '</div>' + '<div class="dev-section-label">📊 Earned (branche hetero)</div>' + '<div class="dev-row">' + '<button class="dev-btn" data-action="earned" data-val="5000">+5k</button>' + '<button class="dev-btn" data-action="earned" data-val="20000">+20k</button>' + '<button class="dev-btn" data-action="earned" data-val="30000">+30k</button>' + '<button class="dev-btn" data-action="earned" data-val="75000">+75k</button>' + '</div>' + '<div class="dev-section-label">🎡 Lucky Wheel</div>' + '<div class="dev-row">' + '<button class="dev-btn" data-action="wheel_ready">Activate Wheel</button>' + '</div>' + '<div class="dev-section-label">🔥 Win Streak (table active)</div>' + '<div class="dev-row">' + '<button class="dev-btn" data-action="streak_plus1">+1 Streak</button>' + '<button class="dev-btn dev-btn-reset" data-action="streak_reset">Reset Streak</button>' + '</div>' + '<div class="dev-section-label">🎲 Consolation Roll</div>' + '<div class="dev-row">' + '<button class="dev-btn" data-action="consolation_now">Show Roll</button>' + '</div>' + '<div class="dev-section-label">🔄 Reset</div>' + '<div class="dev-row">' + '<button class="dev-btn dev-btn-reset" data-action="reset_tips">Reset Tips</button>' + '<button class="dev-btn dev-btn-reset" data-action="reset_session">Reset Session</button>' + '<button class="dev-btn dev-btn-reset" data-action="reset_tables">Reset Tables</button>' + '</div>' + '<div id="dev-panel-feedback"></div>' + '<button id="dev-close-btn">✕ Close</button>'; document.body.appendChild(panel); function feedback(msg) { var el = document.getElementById('dev-panel-feedback'); if (el) { el.textContent = msg; setTimeout(function() { el.textContent = ''; }, 1500); } } panel.querySelectorAll('.dev-btn').forEach(function(btn) { btn.addEventListener('click', function() { var v = State.variables; var action = this.getAttribute('data-action'); var val = parseInt(this.getAttribute('data-val')) || 0; if (action === 'money') { v.playerMoney += val; feedback('✦ +$' + val.toLocaleString() + ' added'); } else if (action === 'tokens') { var tbl = v.activeTable || 'vice'; if (!v.tokens) v.tokens = {}; v.tokens[tbl] = (v.tokens[tbl] || 0) + val; feedback('✦ +' + val + ' tokens → ' + tbl); } else if (action === 'earned') { v.earnedHetero = (v.earnedHetero || 0) + val; v.totalEarned = (v.totalEarned || 0) + val; feedback('✦ earnedHetero +' + val.toLocaleString()); } else if (action === 'wheel_ready') { v.wheelHandsCount[v.activeBranch] = 20; v.wheelReady[v.activeBranch] = true; feedback('✦ Lucky Wheel activated!'); /* Refresh wheel button in sidebar */ var wheelBtn = document.getElementById('nsb-btn-wheel'); if (wheelBtn) { wheelBtn.classList.add('nsb-btn-wheel-claim'); wheelBtn.innerHTML = '<div class="nsb-wheel-top"><span class="nsb-wheel-icon">🎡</span> ✦ Claim Reward</div>'; wheelBtn.style.borderColor = 'rgba(255,215,0,0.6)'; wheelBtn.style.background = 'rgba(255,215,0,0.1)'; wheelBtn.style.color = 'var(--gold)'; } } else if (action === 'streak_plus1') { var tbl = v.activeTable || 'vice'; if (!v.currentStreak) v.currentStreak = {}; v.currentStreak[tbl] = (v.currentStreak[tbl] || 0) + 1; feedback('✦ Streak → ' + v.currentStreak[tbl] + ' on ' + tbl); } else if (action === 'streak_reset') { var tbl = v.activeTable || 'vice'; if (v.currentStreak) v.currentStreak[tbl] = 0; feedback('✦ Streak reset on ' + tbl); } else if (action === 'consolation_now') { v.consolationPending = { table: v.activeTable || 'vice', branch: v.activeBranch || 'hetero', losses: 4 }; window.showLossConsolation(); feedback('✦ Consolation roll shown'); } else if (action === 'reset_tips') { v.visitedPages = {}; feedback('✦ All tips reset'); } else if (action === 'reset_session') { v.scarlettSeenThisSession = false; v.transHostSeenThisSession = false; v.firstWinSeen = false; feedback('✦ Session flags reset'); } else if (action === 'reset_tables') { v.tableUnlockSeen = { boudoir:false, heat:false, slutpit:false, heterovip:false, penthouse:false }; feedback('✦ Table unlock tips reset'); } /* Refresh sidebar balance display */ var balEl = document.querySelector('.nsb-balance-val'); if (balEl) balEl.textContent = '$' + (State.variables.playerMoney || 0).toLocaleString(); }); }); document.getElementById('dev-close-btn').addEventListener('click', function() { panel.remove(); }); } /* Écouter Ctrl+Shift+D */ document.addEventListener('keydown', function(e) { if (e.ctrlKey && e.shiftKey && e.key === 'D') { e.preventDefault(); if (document.getElementById(DEV_PANEL_ID)) { document.getElementById(DEV_PANEL_ID).remove(); } else { buildDevPanel(); } } }); })(); /* ══════════════════════════════════════════ HOTESSES PAR MONDE Scarlett (Straight) et Coco (Trans) parlent dans toutes les bulles d'aide, le lot de consolation, l'accueil et le retour de sauvegarde. Pour renommer l'hotesse trans : changer name ci-dessous (et ses repliques ecrites a la premiere personne si elles la nomment). ══════════════════════════════════════════ */ window.HOSTS = { hetero: { id:'hetero', name:'Scarlett', img:'Images/npc/welcomer_straight.png', sub:'Your guide', world:'Straight' }, trans: { id:'trans', name:'Coco', img:'Images/npc/welcomer_trans.png', sub:'Your hostess', world:'Trans' } }; window.currentHost = function(branch) { var b = branch || (State.variables && State.variables.activeBranch) || 'hetero'; return window.HOSTS[b] || null; }; /* En-tete portrait + nom, commun a toutes les bulles d'hotesse */ window.hostHeader = function(host, sub) { return '<div class="fvt-scarlett-header">' + '<img src="' + host.img + '" class="fvt-scarlett-avatar" alt="' + host.name + '">' + '<div class="fvt-scarlett-info">' + '<span class="fvt-scarlett-name">✦ ' + host.name + '</span>' + '<span class="fvt-scarlett-sub">' + (sub || host.sub) + '</span>' + '</div>' + '</div>'; }; window.hostBoxClass = function(host) { return host ? ' fvt-box-scarlett fvt-host-' + host.id : ''; }; window.showFirstVisitTip = function(pageKey, cfg) { var v = State.variables; if (!v.visitedPages) v.visitedPages = {}; var host = window.currentHost(); /* Chaque hotesse presente elle-meme chaque ecran : le monde Trans a ses propres bulles (cle suffixee), le Straight garde ses cles. */ if (host && host.id === 'trans') pageKey = pageKey + '_trans'; if (v.visitedPages[pageKey]) return; v.visitedPages[pageKey] = true; var hostBody = !host ? null : host.id === 'trans' ? (cfg.bodyTrans || cfg.bodyScarlett) : cfg.bodyScarlett; var overlay = document.createElement('div'); overlay.className = 'fvt-overlay'; var scarlettHeader = host ? window.hostHeader(host) : '<div class="fvt-icon">' + (cfg.icon || '💡') + '</div>'; var closeLabel = cfg.closeBtnLabel || 'Got it ✓'; var box = document.createElement('div'); box.className = 'fvt-box' + window.hostBoxClass(host); box.innerHTML = scarlettHeader + '<div class="fvt-title">' + cfg.title + '</div>' + '<div class="fvt-body">' + (hostBody || cfg.body) + '</div>' + (cfg.tips ? '<ul class="fvt-tips">' + cfg.tips.map(function(t) { return '<li>' + t + '</li>'; }).join('') + '</ul>' : '') + '<button class="fvt-btn fvt-close-btn-inner">' + closeLabel + '</button>'; overlay.appendChild(box); document.body.appendChild(overlay); function close() { overlay.remove(); if (cfg.onClose) cfg.onClose(); } box.querySelector('.fvt-close-btn-inner').addEventListener('click', close); overlay.addEventListener('click', function(e) { if (e.target === overlay) close(); }); }; /* ══════════════════════════════════════════ LOT DE CONSOLATION (surprise, non documentee) Une serie de mains perdues sur une meme table peut declencher un lancer de de. Rien n'est garanti : a chaque nouvelle defaite a partir de LOSS_MIN, une chance est tiree. Apres un declenchement, la serie repart de zero et un delai (en mains jouees dans le monde) empeche tout nouveau lot. ══════════════════════════════════════════ */ window.LOSS_MIN = 3; window.LOSS_CHANCE = { 3:0.08, 4:0.15, 5:0.25, 6:0.35 }; /* 6 = 6 et plus */ window.LOSS_COOLDOWN = 10; /* Faces 1 a 5 : argent (memes montants sur toutes les tables). Face 6 : pack. */ window.LOSS_DIE_MONEY = [50, 75, 100, 150, 250]; window.updateLossStreak = function(mainRes, splitRes) { var v = State.variables; var tbl = v.activeTable || 'vice'; var br = v.activeBranch || 'hetero'; if (typeof v.lossStreak !== 'object' || v.lossStreak === null) v.lossStreak = {}; if (typeof v.consolationCooldown !== 'object' || v.consolationCooldown === null) v.consolationCooldown = { hetero:0, gay:0, trans:0 }; function isLoss(r) { return r === 'lose' || r === 'bust'; } function isWin(r) { return r === 'win' || r === 'blackjack'; } if ((v.consolationCooldown[br] || 0) > 0) v.consolationCooldown[br]--; var lost = isLoss(mainRes) && (splitRes == null || isLoss(splitRes)); var won = isWin(mainRes) || isWin(splitRes); if (won) { v.lossStreak[tbl] = 0; return; } if (!lost) { return; } /* egalite : la serie ne bouge pas */ var n = (v.lossStreak[tbl] || 0) + 1; v.lossStreak[tbl] = n; if (n < window.LOSS_MIN || v.consolationPending || (v.consolationCooldown[br] || 0) > 0) return; var chance = window.LOSS_CHANCE[Math.min(n, 6)] || 0; if (Math.random() < chance) { v.consolationPending = { table: tbl, branch: br, losses: n }; v.lossStreak[tbl] = 0; v.consolationCooldown[br] = window.LOSS_COOLDOWN; } }; /* {n} est remplace par le nombre de mains perdues d'affilee */ window.LOSS_LINES_SCARLETT = [ '{n} hands lost in a row? Oh, sweetheart. That was painful to watch. Here, roll this before you start crying on my felt.', '{n} losses on the trot. I\'d call it bad luck, but I\'ve been watching you play. Take a roll. On the house.', 'The cards have beaten you {n} times straight. I don\'t hate you. Not entirely. Roll the die.', 'I don\'t usually do charity, but {n} losses in a row is hard to ignore. Consider this an exception, and don\'t make it a habit.', '{n} hands, {n} losses. You look like you need a drink. I\'m offering something better. Go on, roll.', 'Even the dealer felt bad after your {n} losses in a row, and he feels nothing. A little something to stop the bleeding.', 'Losing {n} hands in a row takes real dedication. I almost admire it. Roll for your consolation prize.', 'Don\'t look at me like that. {n} losses in a row and I\'m still here. I\'m not saving you, I\'m protecting my favourite customer. Roll.' ]; window.LOSS_LINES_TRANS = [ '{n} hands lost in a row? Oh honey, come here. Roll this, let\'s fix that mood.', '{n} losses straight. The cards are being mean to you tonight. I\'m not. Roll the die.', 'Sweetie, {n} in a row is just cruel. Let me make it up to you. Roll.', 'I saw those {n} losses. Don\'t pout, it ruins that pretty face. Here, a little consolation.', '{n} hands, {n} losses. Even my mascara would run. Roll the die, it\'s on me.', 'A {n}-hand losing streak. Lucky for you, I have a soft spot for sore losers. Roll.' ]; /* Monde sans hotesse (Gay pour l'instant) : la maison parle */ window.LOSS_LINES_HOUSE = [ '{n} hands lost in a row. The house noticed. Here\'s a little something to turn it around.', 'A {n}-hand losing streak. The house is feeling generous tonight. Roll the die.', '{n} losses in a row. Luck owes you one. Time to collect.', 'You just lost {n} hands straight. Let\'s see if the die likes you better than the cards.', 'After {n} losses in a row, a small consolation from the house. Roll and see what you get.', 'Even the best players hit a cold run, and {n} hands in a row is cold. Take a roll on the house.' ]; window.showLossConsolation = function() { var v = State.variables; var pend = v.consolationPending; if (!pend || document.getElementById('lc-overlay')) return; var host = window.currentHost(pend.branch || v.activeBranch); var pack = (window.PACKS || []).find(function(p) { return p.table === pend.table && !p.forcedRarity; }); var faces = window.LOSS_DIE_MONEY.map(function(m) { return { type:'money', value: m }; }); faces.push({ type:'pack', value: 1 }); var lines = !host ? window.LOSS_LINES_HOUSE : host.id === 'trans' ? window.LOSS_LINES_TRANS : window.LOSS_LINES_SCARLETT; var losses = pend.losses || window.LOSS_MIN; var line = lines[Math.floor(Math.random() * lines.length)].replace(/\{n\}/g, losses); /* Points du de : positions dans une grille 3x3 (0..8) */ var PIPS = { 1:[4], 2:[0,8], 3:[0,4,8], 4:[0,2,6,8], 5:[0,2,4,6,8] }; function dieFace(n) { if (n === 6) return '<div class="lc-die-face lc-die-pack">🎴</div>'; var html = '<div class="lc-die-face">'; for (var i = 0; i < 9; i++) html += '<i' + (PIPS[n].indexOf(i) !== -1 ? ' class="on"' : '') + '></i>'; return html + '</div>'; } function faceLabel(f) { return f.type === 'pack' ? 'Free pack' : formatMoney(f.value); } var header = host ? window.hostHeader(host, host.id === 'trans' ? 'Spoiling you a little' : 'Taking pity on you') : '<div class="fvt-icon">🎲</div>'; var overlay = document.createElement('div'); overlay.id = 'lc-overlay'; overlay.className = 'fvt-overlay'; overlay.innerHTML = '<div class="fvt-box lc-box' + window.hostBoxClass(host) + '">' + header + '<div class="fvt-title">Consolation Roll</div>' + '<div class="fvt-body">' + line + '</div>' + '<div class="lc-stage"><div class="lc-die" id="lc-die"><div class="lc-die-face lc-die-q">?</div></div></div>' + '<div class="lc-faces">' + faces.map(function(f, i) { return '<div class="lc-face-chip' + (f.type === 'pack' ? ' lc-face-pack' : '') + '" data-face="' + (i + 1) + '">' + '<span class="lc-chip-die">' + dieFace(i + 1) + '</span>' + '<span class="lc-chip-label">' + faceLabel(f) + '</span>' + '</div>'; }).join('') + '</div>' + '<div class="lc-result" id="lc-result"></div>' + '<button class="fvt-btn" id="lc-roll">🎲 Roll the die</button>' + '</div>'; document.body.appendChild(overlay); var dieEl = overlay.querySelector('#lc-die'); var rollBt = overlay.querySelector('#lc-roll'); var resEl = overlay.querySelector('#lc-result'); var gotPack = false; rollBt.addEventListener('click', function() { if (rollBt.dataset.state === 'rolling') return; if (rollBt.dataset.state === 'done') { finish(); return; } rollBt.dataset.state = 'rolling'; rollBt.disabled = true; v.consolationPending = null; /* un seul lancer, meme si on recharge */ var result = Math.floor(Math.random() * 6) + 1; var face = faces[result - 1]; /* Pas de pack possible (table sans cartes) : la face 6 paie la plus grosse somme */ if (face.type === 'pack' && !pack) face = faces[4]; dieEl.classList.add('lc-rolling'); var ticks = 0; var timer = setInterval(function() { dieEl.innerHTML = dieFace(Math.floor(Math.random() * 6) + 1); if (++ticks >= 12) { clearInterval(timer); dieEl.classList.remove('lc-rolling'); dieEl.classList.add('lc-landed'); dieEl.innerHTML = dieFace(result); apply(face, result); } }, 90); }); function apply(face, n) { var chip = overlay.querySelector('.lc-face-chip[data-face="' + n + '"]'); if (chip) chip.classList.add('lc-face-hit'); if (face.type === 'pack') { var ok = window.openPack(pack.id, true); if (ok === true) { gotPack = true; resEl.innerHTML = 'You win a <strong>free ' + pack.emoji + ' ' + pack.name + '</strong>'; rollBt.textContent = '🎴 Open Pack'; } else { face = faces[4]; } } if (face.type === 'money') { v.playerMoney += face.value; resEl.innerHTML = 'You win <strong>+' + formatMoney(face.value) + '</strong>'; rollBt.textContent = 'Collect ✓'; var hudVals = document.querySelectorAll('#game-hud .hud-stat-value'); if (hudVals[0]) hudVals[0].textContent = formatMoney(v.playerMoney); } resEl.classList.add('lc-result-on'); if (typeof buildNavSidebar === 'function') buildNavSidebar(); rollBt.dataset.state = 'done'; rollBt.disabled = false; } function finish() { overlay.remove(); if (gotPack) Engine.play('Open Pack'); } }; /* ══════════════════════════════════════════ LUCKY WHEEL — Segments par branche ══════════════════════════════════════════ */ /* ══════════════════════════════════════════ BANDEAU DE BRANCHE Identifie clairement le monde courant en haut des 4 ecrans (Casino, Card Shop, My Collection, Sell Cards). Les visuels attendus : Images/icons/<branche>_visual.png ══════════════════════════════════════════ */ /* ══════════════════════════════════════════ TABLES NON DEVOILEES Leur nom, leur visuel et leurs categories sont masques dans le Casino ET dans le Card Shop. Passe REVEAL_ALL_TABLES a true pour tout afficher (utile pour verifier les visuels avant publication). ══════════════════════════════════════════ */ window.MYSTERY_TABLES = ['velvet', 'dollhouse', 'herturn', 'trance', 'transvip']; window.REVEAL_ALL_TABLES = false; window.mysteryTables = function() { return window.REVEAL_ALL_TABLES ? [] : (window.MYSTERY_TABLES || []); }; window.BRANCH_INFO = { hetero: { label:'Straight', visual:'Images/icons/straight_visual.png', color:'#ff2d78', emoji:'\u{1F525}' }, gay: { label:'Gay', visual:'Images/icons/gay_visual.png', color:'#a855f7', emoji:'\u{1F308}' }, trans: { label:'Trans', visual:'Images/icons/trans_visual.png', color:'#f9a8d4', emoji:'\u{1F49C}' } }; window.branchBanner = function(screenLabel, rightHtml) { var b = State.variables.activeBranch || 'hetero'; var info = window.BRANCH_INFO[b] || window.BRANCH_INFO.hetero; return '<div class="brand-bar brand-bar-' + b + '" style="--brand:' + info.color + ';">' + '<div class="brand-visual">' + '<img src="' + info.visual + '" alt="' + info.label + '" ' + 'onerror="this.style.display=\'none\';this.parentNode.classList.add(\'brand-visual-fallback\');">' + '<span class="brand-visual-emoji">' + info.emoji + '</span>' + '</div>' + '<div class="brand-text">' + '<div class="brand-world">' + info.label + '</div>' + '<div class="brand-screen">' + screenLabel + '</div>' + '</div>' + (rightHtml ? '<div class="brand-aside">' + rightHtml + '</div>' : '') + '</div>'; }; window.WHEEL_SEGMENTS = { hetero: [ { type:'money', value:100, label:'$100', color:'#7a5a00' }, { type:'tokens', value:50, label:'50 tkn', color:'#005a66' }, { type:'money', value:200, label:'$200', color:'#9d1a00' }, { type:'card', value:1, label:'1 Pack', color:'#5c008f' }, { type:'money', value:150, label:'$150', color:'#7a5a00' }, { type:'tokens', value:100, label:'100 tkn', color:'#005a66' }, { type:'money', value:300, label:'$300', color:'#9d1a00' }, { type:'money', value:100, label:'$100', color:'#7a5a00' }, { type:'tokens', value:75, label:'75 tkn', color:'#005a66' }, { type:'money', value:500, label:'$500 🌟', color:'#c4005e' } ], gay: [ { type:'money', value:100, label:'$100', color:'#4a0080' }, { type:'tokens', value:50, label:'50 tkn', color:'#2d0060' }, { type:'money', value:200, label:'$200', color:'#6b0099' }, { type:'card', value:1, label:'1 Pack', color:'#1e0044' }, { type:'money', value:150, label:'$150', color:'#4a0080' }, { type:'tokens', value:100, label:'100 tkn', color:'#2d0060' }, { type:'money', value:300, label:'$300', color:'#6b0099' }, { type:'money', value:100, label:'$100', color:'#4a0080' }, { type:'tokens', value:75, label:'75 tkn', color:'#2d0060' }, { type:'money', value:500, label:'$500 🌟', color:'#a855f7' } ], trans: [ { type:'money', value:100, label:'$100', color:'#80003a' }, { type:'tokens', value:50, label:'50 tkn', color:'#660030' }, { type:'money', value:200, label:'$200', color:'#99004a' }, { type:'card', value:1, label:'1 Pack', color:'#3d001f' }, { type:'money', value:150, label:'$150', color:'#80003a' }, { type:'tokens', value:100, label:'100 tkn', color:'#660030' }, { type:'money', value:300, label:'$300', color:'#99004a' }, { type:'money', value:100, label:'$100', color:'#80003a' }, { type:'tokens', value:75, label:'75 tkn', color:'#660030' }, { type:'money', value:500, label:'$500 🌟', color:'#f9a8d4' } ] }; /* Pool de cartes par branche pour la roue */ window.WHEEL_CARD_CATEGORIES = { hetero: ["girl_strip","girl_ass","tits","pussy_eating","handjob","anal","blowjob","fingering","tits_fuck","fucking","rimjob","dp","bbc","creampie","threesome","gangbang","cumshot"], gay: ["gay_solo","gay_scenes"], trans: ["ts_tease","ts_solo","ts_handjob"] }; /* Tirer une carte pour la roue (depuis le pool de la branche) */ window.drawWheelCard = function(branch) { var v = State.variables; var cats = window.WHEEL_CARD_CATEGORIES[branch] || window.WHEEL_CARD_CATEGORIES.hetero; var rarity = window.rollRarity(null); var pool = window.CARD_DB.filter(function(c) { return cats.indexOf(c.category) !== -1 && c.rarity === rarity; }); if (!pool.length) pool = window.CARD_DB.filter(function(c) { return cats.indexOf(c.category) !== -1; }); if (!pool.length) return null; var card = pool[Math.floor(Math.random() * pool.length)]; if (v.collection[card.id] === undefined) v.collection[card.id] = 0; v.collection[card.id]++; return card; }; function buildNavSidebar() { var old = document.getElementById('nav-sidebar'); if (old) old.remove(); var v = State.variables; if (!v) return; var branchColors = { hetero:'var(--neon-pink)', gay:'#a855f7', trans:'#f9a8d4' }; var branchNames = { hetero:'Straight', gay:'Gay', trans:'Trans' }; var branchIcons = { hetero:'Images/icons/straight_icon.png', gay:'Images/icons/gay_icon.png', trans:'Images/icons/trans_icon.png' }; var branchColor = branchColors[v.activeBranch] || 'var(--neon-pink)'; var branchName = branchNames[v.activeBranch] || ''; var branchIcon = branchIcons[v.activeBranch] || ''; var earnedVal = v.activeBranch === 'gay' ? v.earnedGay : v.activeBranch === 'trans' ? v.earnedTrans : v.earnedHetero; var sidebar = document.createElement('div'); sidebar.id = 'nav-sidebar'; /* Jetons de la table active */ if (!v.tokens) v.tokens = { vice:0, boudoir:0, heat:0, slutpit:0, heterovip:0, rainbow:0, gayvip:0, transroom:0, velvet:0, dollhouse:0, herturn:0, trance:0, transvip:0, penthouse:0 }; var activeTable = v.activeTable || 'vice'; var activeTokens = v.tokens[activeTable] || 0; var tableObj = window.TABLES ? window.TABLES.find(function(t) { return t.id === activeTable; }) : null; var tableLabel = tableObj ? tableObj.name : activeTable; var tableEmoji = tableObj ? tableObj.emoji : '🎫'; /* Doublons prets a etre vendus dans le monde actif (pastille Sell Cards) */ var dupeCount = (function() { var br = v.activeBranch || 'hetero'; var col = v.collection || {}; var tags = (window.CATEGORIES || []) .filter(function(c) { return c.branch === br; }) .map(function(c) { return c.tag; }); return (window.CARD_DB || []).reduce(function(sum, c) { var n = col[c.id] || 0; return (n >= 2 && tags.indexOf(c.category) !== -1) ? sum + (n - 1) : sum; }, 0); })(); var dupeNotif = dupeCount > 0 ? '<span class="nsb-notif" title="' + dupeCount + ' duplicate' + (dupeCount === 1 ? '' : 's') + ' ready to sell">' + (dupeCount > 99 ? '99+' : dupeCount) + '</span>' : ''; var wBranch = v.activeBranch || 'hetero'; var wCount = Math.min((v.wheelHandsCount && v.wheelHandsCount[wBranch]) || 0, 20); var wReady = (v.wheelReady && v.wheelReady[wBranch]) || false; var wPct = Math.round((wCount / 20) * 100); var wheelBtnHtml = wReady ? '<a class="nsb-btn nsb-btn-wheel nsb-btn-wheel-ready" id="nsb-btn-wheel">' + '<span class="nsb-wheel-icon">🎡</span> Lucky Wheel' + '</a>' : '<a class="nsb-btn nsb-btn-wheel nsb-btn-wheel-locked" id="nsb-btn-wheel">' + '<div class="nsb-wheel-top"><span class="nsb-wheel-icon">🎡</span> Lucky Wheel</div>' + '<div class="nsb-wheel-progress-wrap">' + '<div class="nsb-wheel-bar" style="width:' + wPct + '%"></div>' + '</div>' + '<span class="nsb-wheel-count">' + wCount + ' / 20 hands</span>' + '</a>'; sidebar.innerHTML = '<div class="nsb-logo">THE <span>21</span> DESIRES</div>' + '<div class="nsb-world-block" style="--branch-color:' + branchColor + ';">' + '<div class="nsb-world-icon-wrap">' + '<img src="' + branchIcon + '" class="nsb-world-icon-img" alt="' + branchName + '" onerror="this.style.opacity=\'0.3\'">' + '<div class="nsb-world-icon-glow"></div>' + '</div>' + '<span class="nsb-world-name" style="color:' + branchColor + ';">' + branchName + '</span>' + '</div>' + '<div class="nsb-sep"></div>' + '<div class="nsb-stat nsb-stat-money">' + '<span class="nsb-label">💰 Balance</span>' + '<span class="nsb-value">' + formatMoney(v.playerMoney) + '</span>' + '</div>' + '<div class="nsb-sep"></div>' + '<div class="nsb-stat nsb-stat-token">' + '<span class="nsb-label">' + tableEmoji + ' ' + tableLabel + '</span>' + '<span class="nsb-value nsb-value-sm nsb-token">🎫 ' + activeTokens + '<span class="nsb-unit">tokens</span></span>' + '</div>' + '<div class="nsb-sep"></div>' + '<div class="nsb-stat nsb-stat-coll">' + '<span class="nsb-label">💎 Collection</span>' + '<span class="nsb-value nsb-value-sm">' + collectionCount() + '<span class="nsb-unit">cards</span></span>' + '</div>' + '<div class="nsb-actions">' + '<a class="nsb-btn" id="nsb-btn-casino">🎰 Casino</a>' + '<a class="nsb-btn" id="nsb-btn-shop">🛍️ Card Shop</a>' + '<a class="nsb-btn" id="nsb-btn-collection">📚 My Collection</a>' + '<div class="nsb-subgroup">' + '<div class="nsb-subgroup-label">Duplicates</div>' + '<a class="nsb-btn nsb-btn-sell" id="nsb-btn-sell">💱 Sell Cards' + dupeNotif + '</a>' + '<a class="nsb-btn nsb-btn-transmute" id="nsb-btn-transmute">💠 Transmute</a>' + '</div>' + '<div class="nsb-divider-thin"></div>' + wheelBtnHtml + '<div class="nsb-divider-thin"></div>' + '<a class="nsb-btn nsb-btn-rules" id="nsb-btn-rules">📖 Rules</a>' + '<a class="nsb-btn nsb-btn-world" id="nsb-btn-world">🌍 Switch World</a>' + '<div class="nsb-divider-patreon"></div>' + '<a class="nsb-btn nsb-btn-patreon" id="nsb-btn-patreon" ' + 'href="' + 'https://patreon.com/Draggora_Studio?utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink' + '" target="_blank" rel="noopener">' + '<img class="nsb-patreon-logo" src="Images/icons/patreon.png" alt="Patreon" ' + 'onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'inline\';">' + '<span class="nsb-patreon-fallback">♥ Patreon</span>' + '</a>' + '</div>'; document.body.appendChild(sidebar); /* ── Si une récompense roue est en attente de claim, bloquer toute navigation ── */ var rewardPending = !!(v._wheelRewardType); if (rewardPending) { /* Ajouter une bannière d'avertissement */ var lockBanner = document.createElement('div'); lockBanner.className = 'nsb-reward-lock'; lockBanner.innerHTML = '🎡 Claim your reward first!'; var actionsDiv = sidebar.querySelector('.nsb-actions'); if (actionsDiv) actionsDiv.insertBefore(lockBanner, actionsDiv.firstChild); /* Désactiver visuellement tous les boutons nav sauf la roue */ ['nsb-btn-casino','nsb-btn-shop','nsb-btn-collection','nsb-btn-sell','nsb-btn-transmute','nsb-btn-rules','nsb-btn-world','nsb-btn-patreon'].forEach(function(id) { var el = document.getElementById(id); if (el) { el.classList.add('nsb-btn-disabled'); el.style.pointerEvents = 'none'; el.style.opacity = '0.25'; } }); /* Rediriger vers la roue si le joueur a quitté sans cliquer Claim */ var wheelBtnEl = document.getElementById('nsb-btn-wheel'); if (wheelBtnEl) { wheelBtnEl.classList.add('nsb-btn-wheel-claim'); wheelBtnEl.innerHTML = '<div class="nsb-wheel-top"><span class="nsb-wheel-icon">🎡</span> ✦ Claim Reward</div>'; wheelBtnEl.style.borderColor = 'rgba(255,215,0,0.6)'; wheelBtnEl.style.background = 'rgba(255,215,0,0.1)'; wheelBtnEl.style.color = 'var(--gold)'; wheelBtnEl.style.animation = 'wheelPulse 1.5s ease-in-out infinite'; wheelBtnEl.addEventListener('click', function() { Engine.play('Spin Wheel'); }); } return; /* Ne pas attacher les autres listeners */ } document.getElementById('nsb-btn-casino').addEventListener('click', function() { Engine.play('Casino'); }); document.getElementById('nsb-btn-shop').addEventListener('click', function() { Engine.play('Card Shop'); }); document.getElementById('nsb-btn-collection').addEventListener('click', function() { Engine.play('My Collection'); }); document.getElementById('nsb-btn-sell').addEventListener('click', function() { Engine.play('Sell Cards'); }); document.getElementById('nsb-btn-transmute').addEventListener('click', function() { Engine.play('Transmute'); }); document.getElementById('nsb-btn-rules').addEventListener('click', function() { Engine.play('Blackjack Rules'); }); document.getElementById('nsb-btn-world').addEventListener('click', function() { Engine.play('World Select'); }); var wheelBtn = document.getElementById('nsb-btn-wheel'); if (wheelBtn) { wheelBtn.addEventListener('click', function() { var _wb = State.variables.activeBranch || 'hetero'; if (State.variables.wheelReady && State.variables.wheelReady[_wb]) Engine.play('Spin Wheel'); }); } } var TABLE_PAGES = ['Blackjack Table', 'Play Hand', 'Dealer Turn', 'Game Result']; /* ══════════════════════════════════════════ AJUSTEMENT AUTOMATIQUE DU TAPIS Calcule la taille des cartes (--card-h) pour que libelles, cartes, totaux et bandeau de resultat tiennent entierement au-dessus de la barre d'actions fixe, quelle que soit la taille de l'ecran. ══════════════════════════════════════════ */ window.CARD_H_MAX = 176; window.CARD_H_MIN = 84; window.fitGameTable = function() { var table = document.querySelector('#passages .game-table'); if (!table) return; var split = !!table.querySelector('.split-zone'); var sideBySide = split && window.innerWidth > 600; var max = window.CARD_H_MAX; var min = window.CARD_H_MIN; /* Ecran etroit : on ne depasse pas ce que la largeur permet */ var w = table.clientWidth || window.innerWidth; var colW = sideBySide ? (w - 80) / 2 : (w - 60); max = Math.min(max, Math.floor(colW / (3 * 0.705 + 0.2))); if (split) max = Math.min(max, 150); if (window.innerWidth <= 600) min = 70; max = Math.max(max, min); function limitY() { var bars = document.querySelectorAll('#result-action-bar, .game-layout .action-bar'); var top = window.innerHeight; Array.prototype.forEach.call(bars, function(b) { var r = b.getBoundingClientRect(); if (r.height > 0 && r.top < top) top = r.top; }); return top - 12; } /* Pages sans bandeau de resultat (main en cours, tour du croupier) : on garde la place du bandeau pour que les cartes ne changent pas de taille d'un ecran a l'autre. */ var isResult = !!table.querySelector('.result-banner'); var reserve = isResult ? 0 : 56; function fits(h) { table.style.setProperty('--card-h', h + 'px'); return table.getBoundingClientRect().bottom + window.scrollY <= limitY() - reserve; } /* Recherche par dichotomie de la plus grande taille qui tient */ var size; if (fits(max)) { size = max; } else { var lo = min, hi = max; while (hi - lo > 2) { var mid = Math.floor((lo + hi) / 2); if (fits(mid)) lo = mid; else hi = mid; } size = lo; } /* Taille commune a toutes les pages de jeu pour cet ecran : on retient la plus petite taille trouvee sur l'ecran de resultat et on ne la depasse plus, pour eviter que les cartes grossissent puis retrecissent. */ var key = window.innerWidth + 'x' + window.innerHeight + (split ? 's' : ''); window._cardHCap = window._cardHCap || {}; if (isResult) { window._cardHCap[key] = Math.min(window._cardHCap[key] || size, size); } if (window._cardHCap[key]) size = Math.min(size, window._cardHCap[key]); table.style.setProperty('--card-h', size + 'px'); }; /* Page de mise : si le contenu depasse la hauteur de l'ecran, on reduit l'ensemble proportionnellement (zoom), sans descendre sous 70 %. */ window.fitBetPage = function() { var page = document.getElementById('passage-blackjack-table'); if (!page) return; page.style.zoom = ''; if (window.innerWidth <= 600) return; /* mobile : on laisse defiler */ var top = page.getBoundingClientRect().top + window.scrollY; var need = page.scrollHeight; var avail = window.innerHeight - top - 16; if (need > avail && avail > 0) { page.style.zoom = Math.max(0.7, avail / need).toFixed(3); } }; (function() { var t = null; function later() { clearTimeout(t); t = setTimeout(function() { window.fitGameTable(); window.fitBetPage(); }, 60); } $(document).on(':passagedisplay', function() { later(); setTimeout(window.fitGameTable, 350); /* apres l'apparition des barres d'actions */ setTimeout(window.fitBetPage, 350); /* apres l'injection du bandeau et de la serie */ }); window.addEventListener('resize', later); })(); $(document).on(':passagerender', function(ev) { var title = ev.passage.title; /* Fond de table — uniquement sur les pages de jeu et de pari */ if (TABLE_PAGES.indexOf(title) === -1) { document.body.style.backgroundImage = ''; document.body.style.backgroundSize = ''; document.body.style.backgroundPosition = ''; document.body.style.backgroundAttachment = ''; } /* Game HUD — uniquement sur les pages de jeu */ if (GAME_PAGES.indexOf(title) === -1) { var hud = document.getElementById('game-hud'); if (hud) hud.remove(); var p = document.getElementById('passages'); if (p) p.classList.remove('game-page'); } /* Barre de résultat — supprimée dès qu'on quitte Game Result */ var rab = document.getElementById('result-action-bar'); if (rab) rab.remove(); /* Nav sidebar */ if (NO_SIDEBAR.indexOf(title) === -1) { buildNavSidebar(); var passages = document.getElementById('passages'); if (passages) { passages.style.marginLeft = '240px'; passages.style.maxWidth = 'calc(100% - 240px)'; passages.style.paddingLeft = '24px'; } } else { var sb = document.getElementById('nav-sidebar'); if (sb) sb.remove(); var passages = document.getElementById('passages'); if (passages) { passages.style.marginLeft = ''; passages.style.maxWidth = ''; passages.style.paddingLeft = ''; } } /* Bouton Credits — réévaluer à chaque passage */ setTimeout(function() { var btn = document.getElementById('btn-credits-save'); if (!btn) return; var v3 = State.variables; var CREDITS_BLOCKED = ['Spin Wheel', 'Game Over', 'Warning', 'Intro', 'How To Play', 'World Select', 'Leave', 'Credits']; var locked = !v3.activeBranch || CREDITS_BLOCKED.indexOf(title) !== -1; btn.disabled = locked; btn.style.opacity = locked ? '0.2' : ''; btn.style.cursor = locked ? 'not-allowed' : ''; btn.title = locked ? 'Choose your world first' : ''; }, 80); }); /* ── SCARLETT LOAD WELCOME ── */ window.HOST_LOAD_LINES = { trans: [ "There you are! I was starting to miss you.", "Welcome back, gorgeous. Your table is all warmed up.", "Back for more? I knew you couldn't stay away.", "Hi again, sweetie. Let's pick up right where we left off.", "Oh good, you're here. It was getting far too quiet.", "Look who's back. Freshen up, the cards are waiting." ] }; window.showScarlettLoadWelcome = function() { var host = window.currentHost(); if (!host) return; var lines = (host.id !== 'hetero' && window.HOST_LOAD_LINES[host.id]) || [ "You're back. I kept your seat warm.", "Took you long enough. The cards missed you.", "Welcome back. Ready to pick up where we left off?", "I knew you'd return. They always do.", "Your seat's been waiting. Let's not waste any more time.", "Back already? Good. I was getting bored without you." ]; var line = lines[Math.floor(Math.random() * lines.length)]; var screen = document.createElement('div'); screen.id = 'scarlett-load-screen'; screen.className = 'host-' + host.id; screen.innerHTML = '<div id="sls-center">' + '<div id="sls-img-wrap">' + '<img src="' + host.img + '" id="sls-img" alt="' + host.name + '">' + '</div>' + '<div id="sls-dialogue">' + '<div id="sls-pretitle">◈ The 21 Desires — ' + host.world + '</div>' + '<div id="sls-name">— ' + host.name + '</div>' + '<div id="sls-text">' + line + '</div>' + '<button id="sls-btn">Enter the Casino ›</button>' + '</div>' + '</div>'; document.body.appendChild(screen); document.getElementById('sls-btn').addEventListener('click', function() { screen.style.opacity = '0'; setTimeout(function() { screen.remove(); }, 400); }); }; /* ── SAVE BAR : injectée une seule fois au démarrage ── */ $(document).one(':passagerender', function() { if (document.getElementById('save-bar')) return; /* ── PANEL SLOTS (affiché au-dessus de la barre) ── */ var slotsPanel = document.createElement('div'); slotsPanel.id = 'slots-panel'; slotsPanel.style.display = 'none'; document.body.appendChild(slotsPanel); var slotsPanelVisible = false; function formatSlotDate(ts) { var d = new Date(ts); return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], { hour:'2-digit', minute:'2-digit' }); } function renderSlotsPanel() { slotsPanel.innerHTML = ''; var title = document.createElement('div'); title.className = 'slots-panel-title'; title.textContent = '💾 Browser Save Slots'; slotsPanel.appendChild(title); var subtitle = document.createElement('div'); subtitle.className = 'slots-panel-subtitle'; subtitle.textContent = 'Saved in this browser only — use Export for cross-device saves'; slotsPanel.appendChild(subtitle); for (var i = 0; i < 3; i++) { (function(slot) { var row = document.createElement('div'); row.className = 'slot-row'; var info = document.createElement('div'); info.className = 'slot-info'; var isEmpty = Save.slots.isEmpty(slot); var slotData = null; try { slotData = isEmpty ? null : Save.slots.get(slot); } catch(e) {} var hasData = !isEmpty && slotData !== null; if (!hasData) { info.innerHTML = '<span class="slot-num">SLOT ' + (slot + 1) + '</span>' + '<span class="slot-empty">— Empty —</span>'; } else { var meta = (slotData && slotData.metadata) ? slotData.metadata : {}; var dateStr = (slotData && slotData.date) ? formatSlotDate(slotData.date) : '—'; info.innerHTML = '<span class="slot-num">SLOT ' + (slot + 1) + '</span>' + '<span class="slot-date">' + dateStr + '</span>' + '<span class="slot-meta">' + '💰 ' + (meta.money !== undefined ? '$' + meta.money.toLocaleString() : '?') + ' · 🎴 ' + (meta.cards !== undefined ? meta.cards + ' cards' : '?') + '</span>'; } var btnSave = document.createElement('button'); btnSave.className = 'btn-slot btn-slot-save'; btnSave.textContent = 'Save'; btnSave.addEventListener('click', function() { var v = State.variables; var meta = { money: v.playerMoney || 0, cards: Object.keys(v.collection || {}).length }; Save.slots.save(slot, 'Slot ' + (slot + 1), meta); showToast('✦ Saved to Slot ' + (slot + 1) + '!', 'success'); renderSlotsPanel(); }); var btnLoad = document.createElement('button'); btnLoad.className = 'btn-slot btn-slot-load'; btnLoad.textContent = 'Load'; btnLoad.disabled = !hasData; if (hasData) { btnLoad.addEventListener('click', function() { var overlay = document.createElement('div'); overlay.id = 'load-overlay'; overlay.style.cssText = 'position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:rgba(6,4,15,0.85);backdrop-filter:blur(6px);'; overlay.innerHTML = '<div style="text-align:center;display:flex;flex-direction:column;align-items:center;gap:14px;">' + '<div style="font-size:2.5rem;">💾</div>' + '<div style="font-family:\'Orbitron\',monospace;font-size:0.9rem;font-weight:700;letter-spacing:0.25em;color:#4fc3f7;text-transform:uppercase;">Save Loaded</div>' + '<div style="font-family:\'Rajdhani\',sans-serif;font-size:1rem;color:rgba(210,200,240,0.7);">Restoring your game...</div>' + '</div>'; document.body.appendChild(overlay); /* Supprimer l'overlay au prochain rendu de passage */ /* Charger la save après l'overlay */ setTimeout(function() { Save.slots.load(slot); if (window.migrateState) window.migrateState(); /* Supprimer l'overlay et afficher Scarlett après le chargement */ setTimeout(function() { var ol = document.getElementById('load-overlay'); if (ol) ol.remove(); window.showScarlettLoadWelcome(); }, 800); }, 1200); }); } var btnDel = document.createElement('button'); btnDel.className = 'btn-slot btn-slot-delete'; btnDel.textContent = '✕'; btnDel.disabled = !hasData; if (hasData) { btnDel.addEventListener('click', function() { Save.slots.delete(slot); showToast('✦ Slot ' + (slot + 1) + ' deleted.', 'success'); renderSlotsPanel(); }); } row.appendChild(info); row.appendChild(btnSave); row.appendChild(btnLoad); row.appendChild(btnDel); slotsPanel.appendChild(row); })(i); } } /* ── BARRE ── */ var bar = document.createElement('div'); bar.id = 'save-bar'; /* Input file caché pour l'import */ var fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.id = 'save-file-input'; fileInput.accept = '.21dsave'; /* Label */ var label = document.createElement('span'); label.className = 'save-label'; label.textContent = '💾 Save'; /* Bouton Slots */ var btnSlots = document.createElement('button'); btnSlots.className = 'btn-save btn-save-slots'; btnSlots.innerHTML = '📂 Slots'; btnSlots.addEventListener('click', function() { slotsPanelVisible = !slotsPanelVisible; if (slotsPanelVisible) { renderSlotsPanel(); slotsPanel.style.display = 'flex'; btnSlots.classList.add('btn-save-slots-active'); } else { slotsPanel.style.display = 'none'; btnSlots.classList.remove('btn-save-slots-active'); } }); /* Fermer le panel si on clique ailleurs */ document.addEventListener('click', function(e) { if (slotsPanelVisible && !slotsPanel.contains(e.target) && e.target !== btnSlots) { slotsPanelVisible = false; slotsPanel.style.display = 'none'; btnSlots.classList.remove('btn-save-slots-active'); } }); /* Bouton Export */ var btnExport = document.createElement('button'); btnExport.className = 'btn-save btn-save-export'; btnExport.innerHTML = '⬇ Export Save'; /* Bouton Import */ var btnImport = document.createElement('button'); btnImport.className = 'btn-save btn-save-import'; btnImport.innerHTML = '⬆ Load Save'; /* Toast feedback */ var toast = document.createElement('span'); toast.className = 'save-toast'; function showToast(msg, type) { toast.textContent = msg; toast.className = 'save-toast ' + type + ' visible'; setTimeout(function() { toast.classList.remove('visible'); }, 2500); } /* EXPORT : sérialiser la save SugarCube → fichier .21dsave */ btnExport.addEventListener('click', function() { try { var data = Save.serialize(); if (!data) throw new Error('No save data'); var blob = new Blob([data], { type: 'text/plain' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'the21desires_' + Date.now() + '.21dsave'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast('✦ Save exported!', 'success'); } catch(e) { showToast('✕ Export failed', 'error'); } }); /* IMPORT : lire le fichier et désérialiser */ btnImport.addEventListener('click', function() { fileInput.click(); }); fileInput.addEventListener('change', function() { var file = fileInput.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function(e) { try { var overlay = document.createElement('div'); overlay.id = 'load-overlay'; overlay.style.cssText = 'position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:rgba(6,4,15,0.85);backdrop-filter:blur(6px);'; overlay.innerHTML = '<div style="text-align:center;display:flex;flex-direction:column;align-items:center;gap:14px;">' + '<div style="font-size:2.5rem;">💾</div>' + '<div style="font-family:\'Orbitron\',monospace;font-size:0.9rem;font-weight:700;letter-spacing:0.25em;color:#4fc3f7;text-transform:uppercase;">Save Loaded</div>' + '<div style="font-family:\'Rajdhani\',sans-serif;font-size:1rem;color:rgba(210,200,240,0.7);">Restoring your game...</div>' + '</div>'; document.body.appendChild(overlay); setTimeout(function() { Save.deserialize(e.target.result); /* Filet de securite : la migration doit tourner meme si le hook Save.onLoad n'a pas ete declenche. */ if (window.migrateState) window.migrateState(); setTimeout(function() { var ol = document.getElementById('load-overlay'); if (ol) ol.remove(); window.showScarlettLoadWelcome(); }, 800); }, 1200); } catch(err) { showToast('✕ Invalid save file', 'error'); } }; reader.readAsText(file); fileInput.value = ''; }); bar.appendChild(fileInput); bar.appendChild(label); bar.appendChild(btnSlots); bar.appendChild(btnExport); bar.appendChild(btnImport); bar.appendChild(toast); /* Bouton Credits — coin droit */ var btnCredits = document.createElement('button'); btnCredits.id = 'btn-credits-save'; btnCredits.className = 'btn-save btn-save-credits'; btnCredits.innerHTML = '✦ Credits'; btnCredits.addEventListener('click', function() { if (!this.disabled) Engine.play('Credits'); }); bar.appendChild(btnCredits); document.body.appendChild(bar); }); <</script>>
<div class="tm-main"> <div class="tm-masthead"> <div class="tm-mast-left"> <div id="tm-brand-bar"></div> <div class="tm-title">Transmute</div> <div class="tm-sub">Break down your extra copies into Essence, then spend it on a card you don't own yet. You always keep one copy of every card.</div> </div> <div class="tm-mast-right"> <div class="tm-stat-label">Your Essence</div> <div class="tm-stat-essence" id="tm-essence">💠 0</div> <div class="tm-stat-gain" id="tm-gain">+0 from 0 spare cards</div> <button class="tm-confirm-btn" id="tm-all-btn">💠 Break down all duplicates</button> </div> </div> <div class="tm-yield-row" id="tm-yield-row"></div> <div id="tm-grid" class="tm-grid"></div> <div id="tm-empty" class="tm-empty"> <div class="tm-empty-icon">🃏</div> <p>You have no duplicate cards to break down.</p> </div> <div class="tm-craft-zone"> <div class="tm-craft-head"> <span class="tm-craft-title">Get a card you're missing</span> <span class="tm-craft-note">Pick a rarity: the card is drawn among those you don't own yet.</span> </div> <div id="tm-craft" class="tm-craft"></div> </div> </div> <<done>> <<script>> (function() { var v = State.variables; if (!v.collection) v.collection = {}; var col = v.collection; var BR = v.activeBranch || 'hetero'; var YIELD = window.ESSENCE_YIELD; var COST = window.ESSENCE_COST; var ORDER = ['common','rare','epic','legendary']; /* Vignettes figees, meme principe que Sell Cards / My Collection */ var FREEZE_TM_THUMBS = true; if (!v.essence) v.essence = { hetero:0, gay:0, trans:0 }; if (typeof v.essence[BR] !== 'number') v.essence[BR] = 0; /* Les mondes sont cloisonnes : on ne touche qu'aux cartes de la branche */ var BR_TAGS = (window.CATEGORIES || []) .filter(function(c) { return c.branch === BR; }) .map(function(c) { return c.tag; }); var BR_CARDS = (window.CARD_DB || []).filter(function(c) { return BR_TAGS.indexOf(c.category) !== -1; }); var grid = document.getElementById('tm-grid'); var emptyEl = document.getElementById('tm-empty'); var craftEl = document.getElementById('tm-craft'); if (emptyEl) emptyEl.style.display = 'none'; var brand = document.getElementById('tm-brand-bar'); if (brand && window.branchBanner) brand.innerHTML = window.branchBanner('Transmute'); function essence() { return v.essence[BR] || 0; } function duplicates() { return BR_CARDS.filter(function(c) { return (col[c.id] || 0) >= 2; }); } function missing(rarity) { return BR_CARDS.filter(function(c) { return c.rarity === rarity && !((col[c.id] || 0) > 0); }); } /* ── Barre des rendements ── */ var yRow = document.getElementById('tm-yield-row'); if (yRow) { yRow.innerHTML = '<span class="tm-yield-legend">Essence per spare</span>' + ORDER.map(function(r) { return '<span class="tm-yield-chip tm-yield-' + r + '">' + rarityIcon(r) + ' ' + window.RARITIES[r].label + ' <b>💠 ' + YIELD[r] + '</b>' + '</span>'; }).join(''); } /* ── Panneau de droite ── */ function updateSummary(dupes) { var spare = dupes.reduce(function(s, c) { return s + (col[c.id] - 1); }, 0); var gain = dupes.reduce(function(s, c) { return s + (col[c.id] - 1) * YIELD[c.rarity]; }, 0); var essEl = document.getElementById('tm-essence'); var gainEl = document.getElementById('tm-gain'); var btn = document.getElementById('tm-all-btn'); if (essEl) essEl.textContent = '💠 ' + essence(); if (gainEl) gainEl.innerHTML = spare ? '<b>+' + gain + '</b> from ' + spare + ' spare card' + (spare === 1 ? '' : 's') : 'No spare cards right now'; if (btn) btn.disabled = (spare === 0); } /* ── Grille des doublons ── */ function renderGrid() { grid.innerHTML = ''; var dupes = duplicates(); updateSummary(dupes); if (!dupes.length) { if (emptyEl) emptyEl.style.display = 'flex'; grid.style.display = 'none'; return; } if (emptyEl) emptyEl.style.display = 'none'; grid.style.display = ''; dupes.forEach(function(card) { var rar = window.RARITIES[card.rarity]; var count = col[card.id] || 0; var extra = count - 1; var el = document.createElement('div'); el.className = 'tm-card'; el.style.borderColor = rar.color; var placeholder = document.createElement('div'); placeholder.className = 'tm-card-placeholder'; placeholder.style.cssText = 'display:none;color:' + rar.color; placeholder.textContent = rarityIcon(card.rarity); var img; if (FREEZE_TM_THUMBS) { img = document.createElement('canvas'); img.className = 'tm-card-img tm-canvas'; img.setAttribute('data-src', card.file); } else { img = document.createElement('img'); img.src = card.file; img.className = 'tm-card-img'; img.alt = card.name; img.onerror = function() { this.style.display = 'none'; placeholder.style.display = 'flex'; }; } var badge = document.createElement('div'); badge.className = 'tm-card-count-badge'; badge.textContent = '\u00d7' + count; var imgWrap = document.createElement('div'); imgWrap.className = 'tm-card-img-wrap'; imgWrap.appendChild(img); imgWrap.appendChild(placeholder); imgWrap.appendChild(badge); var body = document.createElement('div'); body.className = 'tm-card-body'; body.innerHTML = '<div class="tm-card-rarity" style="color:' + rar.color + '">' + rarityIcon(card.rarity) + ' ' + rar.label + '</div>' + '<div class="tm-card-name">' + card.name + '</div>' + '<div class="tm-card-meta">' + '<span>\u00d7' + extra + ' spare</span>' + '<span class="tm-card-gain">💠 +' + (extra * YIELD[card.rarity]) + '</span>' + '</div>' + '<button class="tm-card-btn">Break down</button>'; body.querySelector('.tm-card-btn').addEventListener('click', function() { col[card.id] = 1; v.essence[BR] += extra * YIELD[card.rarity]; refresh(); }); el.appendChild(imgWrap); el.appendChild(body); grid.appendChild(el); }); if (FREEZE_TM_THUMBS) chargerVignettesTm(); } /* ── Peinture des vignettes figees ── */ var tmObserver = null; function peindreVignetteTm(cv) { if (cv.dataset.done) return; cv.dataset.done = '1'; var src = cv.getAttribute('data-src'); var im = new Image(); im.onload = function() { var w = im.naturalWidth || 300; var h = im.naturalHeight || 400; cv.width = w; cv.height = h; try { cv.getContext('2d').drawImage(im, 0, 0, w, h); } catch (e) { var fb = document.createElement('img'); fb.className = 'tm-card-img'; fb.src = src; if (cv.parentNode) cv.parentNode.replaceChild(fb, cv); } }; im.onerror = function() { cv.style.display = 'none'; var ph = cv.parentNode && cv.parentNode.querySelector('.tm-card-placeholder'); if (ph) ph.style.display = 'flex'; }; im.src = src; } function chargerVignettesTm() { var cvs = grid.querySelectorAll('canvas.tm-canvas'); if (!cvs.length) return; if (typeof IntersectionObserver === 'undefined') { Array.prototype.forEach.call(cvs, peindreVignetteTm); return; } if (tmObserver) tmObserver.disconnect(); tmObserver = new IntersectionObserver(function(entries, obs) { entries.forEach(function(en) { if (en.isIntersecting) { peindreVignetteTm(en.target); obs.unobserve(en.target); } }); }, { root: null, rootMargin: '300px', threshold: 0 }); Array.prototype.forEach.call(cvs, function(cv) { tmObserver.observe(cv); }); } /* ── Boutons d'achat d'une carte manquante ── */ function renderCraft() { craftEl.innerHTML = ''; ORDER.forEach(function(r) { var rar = window.RARITIES[r]; var pool = missing(r); var cost = COST[r]; var possible = pool.length > 0; var afford = essence() >= cost; var state = !possible ? 'All collected' : !afford ? 'Need 💠 ' + (cost - essence()) + ' more' : pool.length + ' missing'; var btn = document.createElement('button'); btn.className = 'tm-craft-btn tm-craft-' + r; btn.style.setProperty('--rc', rar.color); btn.disabled = !(possible && afford); btn.innerHTML = '<span class="tm-craft-rar">' + rarityIcon(r) + ' ' + rar.label + '</span>' + '<span class="tm-craft-cost">💠 ' + cost + '</span>' + '<span class="tm-craft-pool">' + state + '</span>'; btn.addEventListener('click', function() { transmute(r); }); craftEl.appendChild(btn); }); } /* ── Transmutation : l'Essence part, la carte entre dans la collection ── */ function transmute(r) { var pool = missing(r); var cost = COST[r]; if (!pool.length || essence() < cost) return; var pick = pool[Math.floor(Math.random() * pool.length)]; v.essence[BR] -= cost; col[pick.id] = (col[pick.id] || 0) + 1; /* carte non possedee -> 1 */ refresh(); showResult(pick); } /* ── Carte obtenue : meme carte que dans le Card Shop, dans une fenetre ── */ function showResult(card) { var old = document.getElementById('tm-result'); if (old) old.remove(); var rar = window.RARITIES[card.rarity]; var info = (window.BRANCH_INFO && window.BRANCH_INFO[BR]) || window.BRANCH_INFO.hetero; var ov = document.createElement('div'); ov.id = 'tm-result'; ov.innerHTML = '<div class="tm-result-backdrop"></div>' + '<div class="tm-result-stage">' + '<div class="tm-result-label" style="color:' + rar.color + ';">' + rarityIcon(card.rarity) + ' ' + rar.label + ' card</div>' + '<div class="big-card-wrapper tm-result-card">' + '<div class="big-card-inner">' + '<div class="big-card-back big-card-back-transmute" style="--brand:#c084fc;">' + '<div class="tmb-prism"></div>' + '<div class="tmb-core"></div>' + '<div class="bcb-pattern"></div>' + '<div class="bcb-frame">' + '<div class="bcb-stack">' + '<div class="bcb-monogram"><span>21</span><em>DESIRES</em></div>' + '<div class="bcb-band">' + '<img src="' + info.visual + '" alt="" ' + 'onerror="this.parentNode.classList.add(\'bcb-band-empty\');">' + '<div class="bcb-band-veil"></div>' + '</div>' + '<div class="bcb-branch"><i></i><span>Guaranteed new</span><i></i></div>' + '</div>' + '</div>' + '<div class="tmb-seal">💠 Transmuted</div>' + '<div class="bcb-sheen"></div>' + '<div class="big-card-back-hint">Click to reveal</div>' + '</div>' + '<div class="big-card-front"></div>' + '</div>' + '</div>' + '<button class="tm-confirm-btn tm-result-continue">Continue</button>' + '</div>'; document.body.appendChild(ov); var wrapper = ov.querySelector('.tm-result-card'); var inner = ov.querySelector('.big-card-inner'); var front = ov.querySelector('.big-card-front'); var cont = ov.querySelector('.tm-result-continue'); wrapper.addEventListener('click', function() { if (inner.classList.contains('flipped')) return; front.innerHTML = '<div class="big-card-face rar-' + card.rarity + '" style="--rar:' + rar.color + ';--rar-glow:' + rar.glow + ';">' + '<div class="bcf-border"></div>' + '<div class="reveal-badge badge-new">✦ NEW</div>' + '<div class="bcf-media">' + '<img src="' + card.file + '" class="bcf-backdrop" alt="" aria-hidden="true" onerror="this.style.display=\'none\';">' + '<img src="' + card.file + '" class="big-card-img" alt="' + card.name + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\';">' + '<div class="big-card-img-placeholder" style="display:none;color:' + rar.color + ';">' + rarityIcon(card.rarity) + '</div>' + '<div class="bcf-shine"></div>' + '</div>' + '<div class="big-card-footer">' + '<span class="big-card-rarity"><i class="bcf-dot"></i>' + rarityIcon(card.rarity) + ' ' + rar.label + '</span>' + '<span class="big-card-name">' + card.name + '</span>' + '</div>' + '</div>'; inner.classList.add('flipped'); ov.classList.add('tm-result-revealed'); }); function close() { if (!inner.classList.contains('flipped')) return; document.removeEventListener('keydown', onKey); ov.remove(); } function onKey(e) { if (e.key === 'Escape' || e.key === 'Enter') close(); } cont.addEventListener('click', close); ov.querySelector('.tm-result-backdrop').addEventListener('click', close); document.addEventListener('keydown', onKey); } /* ── Tout desassembler ── */ document.getElementById('tm-all-btn').addEventListener('click', function() { var dupes = duplicates(); if (!dupes.length) return; dupes.forEach(function(c) { v.essence[BR] += (col[c.id] - 1) * YIELD[c.rarity]; col[c.id] = 1; }); refresh(); }); function refresh() { renderGrid(); renderCraft(); if (typeof buildNavSidebar === 'function') buildNavSidebar(); } refresh(); /* ── Message d'explication, une fois par monde ── */ window.showFirstVisitTip('transmute', { icon: '💠', title: 'Transmute', body: 'Break down your duplicate cards into Essence, then spend that Essence on a card you don\'t own yet. You choose the rarity, the card is drawn among the ones you\'re missing.', bodyScarlett: 'Doubles piling up? Bring them to me. I melt the extras down into Essence, and you always keep one copy of each. Save up enough and I\'ll find you a card you\'ve never seen. You pick the rarity, I pick the card.', bodyTrans: 'Duplicates piling up? Hand them over, I melt them into Essence, and you always keep one copy of each. Save enough and I\'ll find you a card you\'ve never seen. You choose the rarity, I choose the surprise.', tips: [ '♻️ Essence per spare: Common ' + YIELD.common + ' · Rare ' + YIELD.rare + ' · Epic ' + YIELD.epic + ' · Legendary ' + YIELD.legendary, '💠 A missing card costs: Common ' + COST.common + ' · Rare ' + COST.rare + ' · Epic ' + COST.epic + ' · Legendary ' + COST.legendary, '✦ A transmuted card is always one you don\'t own yet', '🌍 Each world keeps its own Essence' ] }); })(); <</script>> <</done>>
<div class="warning-screen"> <div class="warning-logo"> <h1 class="game-title warning-title">THE <span>21</span> DESIRES</h1> <p class="warning-tagline">◈ Adult Content Warning ◈</p> </div> <div class="warning-box"> <div class="warning-icon-wrap"> <span class="warning-icon">⚠️</span> <span class="warning-icon-label">Before you enter</span> </div> <div class="warning-items"> <div class="warning-item"> <span class="warning-num">01</span> <p>All characters portrayed in this game are <strong>18 years of age or older</strong>.</p> </div> <div class="warning-item"> <span class="warning-num">02</span> <p>This game contains <strong>explicit sexual content</strong> including nudity, adult animations, and sexually graphic material. Viewer discretion is advised.</p> </div> <div class="warning-item"> <span class="warning-num">03</span> <p>Some images or media used may belong to third parties. If you are the owner of any content displayed in <em>The 21 Desires</em> and wish to have it removed, please contact the developer.</p> </div> <div class="warning-item"> <span class="warning-num">04</span> <p>This is a work of <strong>fiction</strong>. All events, characters, and interactions are entirely fabricated and bear no relation to real-world persons or situations.</p> </div> </div> <div class="warning-divider"></div> <p class="warning-consent">By continuing, you confirm that you are of <strong>legal age</strong> to view <strong>adult content</strong> in your country, and that you have read and accepted the above.</p> <div class="warning-buttons"> <<link "✦ I am 18 or older — Enter" "Intro">><</link>> <a href="https://www.google.com" class="btn-warning-leave">✕ I am under 18 — Leave</a> </div> </div> </div> <<done>> <<script>> (function() { const btns = document.querySelectorAll('.warning-buttons a'); if (btns[0]) btns[0].classList.add('btn', 'btn-primary', 'btn-warning-enter'); if (btns[1]) btns[1].classList.add('btn', 'btn-warning-leave'); })(); <</script>> <</done>> <style> /* Garde-fou : SugarCube convertit CHAQUE retour à la ligne du passage en <br>, y compris entre éléments block (<div>) — pas seulement autour des éléments inline comme on le pensait initialement. Dans un conteneur flex/grid, ce <br> devient un élément à part entière : au mieux il ajoute un espacement fantôme (flex, via `gap`), au pire il décale toute la grille dans une seule colonne (cas vécu sur .warning-items). On neutralise donc `br` dans TOUS les conteneurs flex/grid de cette page. */ .warning-screen br, .warning-logo br, .warning-box br, .warning-icon-wrap br, .warning-items br, .warning-item br, .warning-buttons br { display: none; } .warning-screen { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px 20px; gap: 18px; } .warning-logo { text-align: center; display: flex; flex-direction: column; align-items: center; gap: 6px; } .warning-title { font-size: clamp(2rem, 6vw, 3.2rem) !important; } .warning-tagline { font-family: 'Share Tech Mono', monospace; font-size: 0.78rem; letter-spacing: 0.45em; color: rgba(200,180,220,0.45); text-transform: uppercase; } .warning-box { max-width: 760px; width: 100%; background: rgba(14,10,30,0.85); border: 1px solid rgba(255,45,120,0.15); border-radius: 20px; padding: 20px 26px 22px; position: relative; backdrop-filter: blur(8px); display: flex; flex-direction: column; gap: 14px; } .warning-box::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px; border-radius: 20px 20px 0 0; background: linear-gradient(90deg, transparent, var(--neon-pink), var(--neon-purple), var(--neon-pink), transparent); } .warning-icon-wrap { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 11px; } .warning-icon { font-size: 1.7rem; line-height: 1; filter: drop-shadow(0 0 12px rgba(255,215,0,0.5)); } .warning-icon-label { font-family: 'Orbitron', monospace; font-size: 0.76rem; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: rgba(255,215,0,0.72); } .warning-items { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } @media (max-width: 680px) { .warning-items { grid-template-columns: 1fr; } } .warning-item { display: flex; gap: 11px; align-items: flex-start; padding: 10px 13px; background: rgba(255,255,255,0.025); border-radius: 10px; border: 1px solid rgba(255,255,255,0.04); } .warning-num { font-family: 'Orbitron', monospace; font-size: 0.7rem; font-weight: 900; color: var(--neon-pink); opacity: 0.9; min-width: 18px; padding-top: 3px; letter-spacing: 0.05em; flex-shrink: 0; } .warning-item p { font-family: 'Rajdhani', sans-serif; font-size: 0.95rem; font-weight: 500; color: rgba(220, 210, 240, 0.82); line-height: 1.45; margin: 0; } .warning-item p strong { color: #fff; font-weight: 700; } .warning-item p em { color: var(--neon-cyan); font-style: normal; font-weight: 600; } .warning-divider { height: 1px; background: linear-gradient(90deg, transparent, rgba(255,45,120,0.2), transparent); margin: 0; } .warning-consent { font-family: 'Rajdhani', sans-serif; font-size: 0.92rem; font-weight: 500; color: rgba(210, 200, 230, 0.65); line-height: 1.45; text-align: center; } .warning-consent strong { color: rgba(255,255,255,0.88); font-weight: 700; } .warning-buttons { display: flex; flex-direction: column; gap: 8px; } .btn-warning-enter { width: 100%; text-align: center; padding: 14px !important; font-size: 1rem !important; letter-spacing: 0.12em !important; } .btn-warning-leave { width: 100%; text-align: center; padding: 10px !important; background: transparent !important; border: 1px solid rgba(255,255,255,0.08) !important; color: rgba(180,170,200,0.4) !important; font-family: 'Rajdhani', sans-serif; font-weight: 600; font-size: 0.85rem; letter-spacing: 0.12em; text-transform: uppercase; border-radius: 8px; transition: all 0.2s; text-shadow: none !important; display: block; cursor: pointer; } .btn-warning-leave:hover { border-color: rgba(255,80,80,0.4) !important; color: rgba(255,110,110,0.75) !important; background: rgba(255,40,40,0.04) !important; text-shadow: none !important; } </style>
<<silently>> <<if $activeBranch === "trans">><<set $transHostSeenThisSession = true>><<else>><<set $scarlettSeenThisSession = true>><</if>> <</silently>> <div id="welcome-straight-screen"> <button id="welcome-skip-btn">Skip ›</button> <div id="welcome-center"> <!-- Image --> <div id="welcome-img-wrap"> <img src="Images/npc/welcomer_straight.png" id="welcome-npc-img" alt="Scarlett"> </div> <!-- Dialogue --> <div id="welcome-dialogue-wrap"> <div id="welcome-game-title">◈ The 21 Desires — Straight</div> <div id="welcome-npc-name">Scarlett</div> <div id="welcome-dialogue-text"></div> <div id="welcome-dialogue-dots"> <span class="wd-dot active"></span> <span class="wd-dot"></span> <span class="wd-dot"></span> </div> <button id="welcome-next-btn">Continue ›</button> </div> </div> </div> <<done>> <<script>> (function() { /* Meme ecran d'accueil pour chaque hotesse (Scarlett / Coco) */ var host = window.currentHost() || window.HOSTS.hetero; var WELCOME_LINES = { hetero: [ "Welcome back\u2026 I\u2019ve been waiting for you.", "I\u2019m Scarlett. I run things around here \u2014 the cards, the odds, and everything in between.", "Take a seat. Let\u2019s see if tonight is your night." ], trans: [ "Well, hello there\u2026 I was hoping you\u2019d find your way to my side of the house.", "I\u2019m Coco. I run the Powder Room, and every secret behind its doors.", "Sit down, get comfortable. Let\u2019s see how lucky you feel tonight." ] }; var lines = WELCOME_LINES[host.id] || WELCOME_LINES.hetero; var screenEl = document.getElementById('welcome-straight-screen'); if (screenEl) screenEl.className = 'host-' + host.id; var imgEl = document.getElementById('welcome-npc-img'); if (imgEl) { imgEl.src = host.img; imgEl.alt = host.name; } var nameEl = document.getElementById('welcome-npc-name'); if (nameEl) nameEl.textContent = host.name; var titleEl = document.getElementById('welcome-game-title'); if (titleEl) titleEl.textContent = '\u25c8 The 21 Desires \u2014 ' + host.world; var current = 0; var textEl = document.getElementById('welcome-dialogue-text'); var nextBtn = document.getElementById('welcome-next-btn'); var skipBtn = document.getElementById('welcome-skip-btn'); var dots = document.querySelectorAll('.wd-dot'); function showLine(i) { if (!textEl) return; textEl.style.opacity = '0'; setTimeout(function() { textEl.textContent = lines[i]; textEl.style.opacity = '1'; }, 200); dots.forEach(function(d, idx) { d.classList.toggle('active', idx === i); }); if (nextBtn) { nextBtn.textContent = (i >= lines.length - 1) ? 'Enter the Casino \u203a' : 'Continue \u203a'; } } showLine(0); if (nextBtn) { nextBtn.addEventListener('click', function() { if (current < lines.length - 1) { current++; showLine(current); } else { Engine.play('Casino'); } }); } if (skipBtn) { skipBtn.addEventListener('click', function() { Engine.play('Casino'); }); } })(); <</script>> <</done>> <style> /* ── Masquer la save bar sur cette page ── */ body:has(#welcome-straight-screen) #save-bar { display: none !important; } #welcome-straight-screen { position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center; background: radial-gradient(ellipse at 70% 50%, rgba(30,10,50,0.95) 0%, rgba(6,4,15,1) 70%); overflow: hidden; } /* Fond décoratif */ #welcome-straight-screen::before { content: ''; position: absolute; inset: 0; background: radial-gradient(ellipse 60% 40% at 75% 60%, rgba(255,45,120,0.06) 0%, transparent 70%), radial-gradient(ellipse 40% 60% at 20% 50%, rgba(191,0,255,0.04) 0%, transparent 70%); pointer-events: none; } /* ── Skip ── */ #welcome-skip-btn { position: absolute; top: 20px; right: 24px; background: transparent; border: 1px solid rgba(255,255,255,0.1); color: rgba(255,255,255,0.3); font-family: 'Share Tech Mono', monospace; font-size: 0.75rem; letter-spacing: 0.2em; padding: 5px 14px; border-radius: 20px; cursor: pointer; transition: all 0.2s; z-index: 10; } #welcome-skip-btn:hover { border-color: rgba(255,255,255,0.25); color: rgba(255,255,255,0.55); } /* ── Conteneur central ── */ #welcome-center { position: relative; z-index: 2; display: flex; align-items: center; gap: 60px; max-width: 1000px; width: 90%; padding: 0 20px; } /* ── Image ── */ #welcome-img-wrap { flex: 0 0 auto; position: relative; } #welcome-npc-img { width: 320px; height: 440px; object-fit: cover; object-position: center top; border-radius: 20px; border: 1px solid rgba(255,45,120,0.2); box-shadow: 0 0 0 1px rgba(255,255,255,0.04), 0 20px 60px rgba(0,0,0,0.7), 0 0 40px rgba(255,45,120,0.1); animation: welcomeImgIn 0.8s ease-out; display: block; } /* Lueur sous l'image */ #welcome-img-wrap::after { content: ''; position: absolute; bottom: -20px; left: 50%; transform: translateX(-50%); width: 200px; height: 30px; background: radial-gradient(ellipse, rgba(255,45,120,0.25), transparent 70%); pointer-events: none; } /* ── Dialogue ── */ #welcome-dialogue-wrap { flex: 1; display: flex; flex-direction: column; align-items: flex-start; animation: welcomeTextIn 0.7s ease-out 0.2s both; } #welcome-game-title { font-family: 'Share Tech Mono', monospace; font-size: 0.68rem; font-weight: 700; letter-spacing: 0.4em; color: rgba(255,45,120,0.45); text-transform: uppercase; margin-bottom: 28px; } #welcome-npc-name { font-family: 'Orbitron', monospace; font-size: 1.1rem; font-weight: 700; letter-spacing: 0.2em; color: var(--neon-pink); text-transform: uppercase; margin-bottom: 18px; text-shadow: 0 0 16px rgba(255,45,120,0.45); display: flex; align-items: center; gap: 12px; } #welcome-npc-name::before { content: ''; display: inline-block; width: 28px; height: 1px; background: var(--neon-pink); opacity: 0.7; } #welcome-dialogue-text { font-family: 'Rajdhani', sans-serif; font-size: 1.2rem; font-weight: 500; color: rgba(240,230,255,0.92); line-height: 1.8; min-height: 80px; max-width: 360px; transition: opacity 0.22s; margin-bottom: 28px; } #welcome-dialogue-dots { display: flex; gap: 7px; margin-bottom: 32px; } .wd-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,45,120,0.2); transition: all 0.3s; } .wd-dot.active { background: var(--neon-pink); box-shadow: 0 0 10px rgba(255,45,120,0.7); width: 22px; border-radius: 4px; } #welcome-next-btn { padding: 13px 32px; background: linear-gradient(135deg, #c4005e, #ff2d78); border: 1px solid rgba(255,45,120,0.5); border-radius: 8px; color: white; font-family: 'Rajdhani', sans-serif; font-weight: 700; font-size: 0.95rem; letter-spacing: 0.15em; text-transform: uppercase; cursor: pointer; transition: all 0.25s; box-shadow: 0 0 24px rgba(255,45,120,0.25); } #welcome-next-btn:hover { background: linear-gradient(135deg, #ff2d78, #ff6ea3); box-shadow: 0 0 40px rgba(255,45,120,0.5); transform: translateY(-2px); } @keyframes welcomeImgIn { from { opacity: 0; transform: translateY(16px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes welcomeTextIn { from { opacity: 0; transform: translateX(16px); } to { opacity: 1; transform: translateX(0); } } </style>
<div class="world-select-screen"> <div class="world-select-title"> <h1 class="game-title">THE <span>21</span> DESIRES</h1> <p class="world-select-subtitle">◈ Choose Your World ◈</p> </div> <div class="world-cards" id="world-cards-container"></div> </div> <<done>> <<script>> (function() { var v = State.variables; var container = document.getElementById('world-cards-container'); if (!container) return; var worlds = [ { id: 'hetero', cls: 'world-card-straight', icon: 'Images/icons/straight_icon.png', name: 'Straight', desc: 'Girls, couples & all hetero fantasies.', unlocked: true }, { id: 'trans', cls: 'world-card-trans', icon: 'Images/icons/trans_icon.png', name: 'Trans', desc: 'Trans, sissy & shemale fantasies.', /* Version publique : carte « Early access on Patreon » (voir TRANS_ENABLED) */ unlocked: !!window.TRANS_ENABLED, earlyAccess: !window.TRANS_ENABLED }, { id: 'gay', cls: 'world-card-gay', icon: 'Images/icons/gay_icon.png', name: 'Gay', desc: 'Men, couples & gay scenes.', unlocked: false, comingSoon: true } ]; worlds.forEach(function(w) { var isActive = v.activeBranch === w.id; var card = document.createElement('div'); card.className = 'world-card ' + w.cls + (w.earlyAccess ? ' world-early-card' : (w.unlocked ? '' : ' world-locked')); var badge = ''; var statusHtml = ''; if (w.earlyAccess) { statusHtml = '<div class="world-early">' + '<span class="world-early-tag">♥ Early access<br>on Patreon</span>' + '<span class="world-early-sub">Coming to everyone soon</span>' + '</div>'; } else if (w.comingSoon) { statusHtml = '<div class="world-coming-soon"><span class="world-cs-icon">✦</span> Coming Soon</div>'; } else if (!w.unlocked) { var pct = Math.min(100, Math.round(w.earned / w.threshold * 100)); statusHtml = '<span class="world-status world-status-locked">🔒 Locked</span>' + '<div class="world-lock-progress">' + '<div class="world-lock-bar-wrap"><div class="world-lock-bar-fill" style="width:' + pct + '%"></div></div>' + '<div class="world-lock-text">' + formatMoney(w.earned) + ' / ' + formatMoney(w.threshold) + ' total earned</div>' + '</div>'; } else { statusHtml = '<span class="world-status world-status-open">✦ Enter</span>'; } card.innerHTML = badge + '<div class="world-icon"><img src="' + w.icon + '" alt="' + w.name + '" class="world-icon-img" onerror="this.style.opacity=\'0.3\'"></div>' + '<div class="world-name">' + w.name + '</div>' + '<div class="world-desc">' + w.desc + '</div>' + statusHtml; /* Click : entrer dans le monde (uniquement si débloqué et pas coming soon) */ if (w.unlocked && !w.comingSoon) { card.addEventListener('click', function() { /* On range la bourse du monde qu'on quitte, on sort celle du monde ou l'on entre. */ if (!v.branchMoney) v.branchMoney = { hetero:1500, gay:1500, trans:1500 }; v.branchMoney[v.activeBranch] = v.playerMoney; v.activeBranch = w.id; v.playerMoney = (typeof v.branchMoney[w.id] === 'number') ? v.branchMoney[w.id] : 1500; if (w.id === 'hetero') { v.activeTable = v.unlockedTables[v.unlockedTables.length - 1] || 'vice'; if (!v.scarlettSeenThisSession) { Engine.play('Welcome Straight'); } else { Engine.play('Casino'); } } else if (w.id === 'gay') { v.activeTable = v.unlockedTablesGay[v.unlockedTablesGay.length - 1] || 'rainbow'; Engine.play('Casino'); } else if (w.id === 'trans') { v.activeTable = v.unlockedTablesTrans[v.unlockedTablesTrans.length - 1] || 'transroom'; if (!v.transHostSeenThisSession) { Engine.play('Welcome Straight'); /* ecran d'accueil commun, version Coco */ } else { Engine.play('Casino'); } } }); } /* Early access : la carte ouvre la page Patreon */ if (w.earlyAccess) { card.title = 'Available now for patrons on Patreon'; card.addEventListener('click', function() { window.open(window.PATREON_URL, '_blank', 'noopener'); }); } container.appendChild(card); }); })(); <</script>> <</done>>